一、进程、线程及多线程的概念

什么是多线程呢?不理解。
那什么是线程呢?说到线程就不得不说说进程。我在网上搜索也搜索了一些资料,大部分所说的进程其实是很抽象的东西。通俗的来讲,进程就是一个应用程序开始运行,那么这个应用程序就会存在一个属于这个应用程序的进程。
那么线程就是进程中的基本执行单元,每个进程中都至少存在着一个线程,这个线程是根据进程创建而创建的,所以这个线程我们称之为主线程。那么多线程就是包含有除了主线程之外的其他线程。如果一个线程可以执行一个任务,那么多线程就是可以同时执行多个任务。
以上的概念纯属个人理解,如有什么不对的地方,还请多多指正。

二、线程的基本知识

Thread 类
Thread 类是用于控制线程的基础类,它存在于 System.Threading 命名空间。通过 Thread 可以控制当前应用程序域中线程的创建、挂起、停止、销毁。
Thread 一些常用属性:
C# 多线程编程第一步——理解多线程 - 图1
Thread 一些常用方法:
C# 多线程编程第一步——理解多线程 - 图2
Thread 的优先级:
C# 多线程编程第一步——理解多线程 - 图3

三、多线程的简单示例

下面就从简单的多线程开始理解吧,这里我创建了一个控制台应用程序。

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. ThreadDemoClass demoClass = new ThreadDemoClass();
  6. //创建一个新的线程
  7. Thread thread = new Thread(demoClass.Run);
  8. //设置为后台线程
  9. thread.IsBackground = true;
  10. //开始线程
  11. thread.Start();
  12. Console.WriteLine("Main thread working...");
  13. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  14. Console.ReadKey();
  15. }
  16. }
  17. public class ThreadDemoClass
  18. {
  19. public void Run()
  20. {
  21. Console.WriteLine("Child thread working...");
  22. Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  23. }
  24. }

创建一个新的线程还可以使用 ThreadStart 委托的方式。如下:
//创建一个委托,并把要执行的方法作为参数传递给这个委托
ThreadStart threadStart = new ThreadStart(demoClass.Run);
Thread thread = new Thread(threadStart);
执行结果:
C# 多线程编程第一步——理解多线程 - 图4
根据以上的结果我们可以分析得到,主线程创建了一个子线程并启动了它,但是主线程没有等到子线程执行完成,而是继续再往下执行的。
这就涉及到了线程异步或同步的问题了,这个我们后面再说。
继续上面的问题,那么如果我想要等到子线程执行完成之后再继续主线程的工作呢(当然,我觉得一般不会有这种需求)。
我们可以使用 Join() 这个方法,修改之后的代码:

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. ThreadDemoClass demoClass = new ThreadDemoClass();
  6. //创建一个新的线程
  7. Thread thread = new Thread(demoClass.Run);
  8. //设置为后台线程
  9. thread.IsBackground = true;
  10. //开始线程
  11. thread.Start();
  12. //等待直到线程完成
  13. thread.Join();
  14. Console.WriteLine("Main thread working...");
  15. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  16. Console.ReadKey();
  17. }
  18. }
  19. public class ThreadDemoClass
  20. {
  21. public void Run()
  22. {
  23. Console.WriteLine("Child thread working...");
  24. Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  25. }
  26. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图5
上面的代码相比之前就添加了一句 thread.Join(),它的作用就是用于阻塞后面的线程,直到当前线程完成之后。当然,还有其他的方法可以做到,比如我们现在把 thread.Join() 换成
下面这句代码。
//挂起当前线程指定的时间
Thread.Sleep(100);
就当前的场景来说,这样的确可以满足需求,但是这样做有一个弊端,就是,当子线程所执行的方法逻辑比较复杂耗时较长的时候,这样的方式就不一定可以,虽然可以修改线程挂起的时间,但是这个执行的时间却是不定的。所以,Thread.Sleep() 方法一般用来设置多线程之间执行的间隔时间的。
另外,Join() 方法也接受一个参数,该参数用于指定阻塞线程的时间,如果在指定的时间内该线程没有终止,那么就返回 false,如果在指定的时间内已终止,那么就返回 true。

上面的这种使用多线程的方式只是简单的输出一段内容而已,多数情况下我们需要对线程调用的方法传入参数和接收返回值的,但是上面这种方法是不接受参数并且没有返回值的,那么我们可以使用 ParameterizedThreadStart 委托来创建多线程,这个委托可以接受一个 object 类型的参数,我们可以在这上面做文章。
//

  1. class Program
  2. {static void Main(string[] args)
  3. {
  4. ThreadDemoClass demoClass = new ThreadDemoClass();
  5. //创建一个委托,并把要执行的方法作为参数传递给这个委托
  6. ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);
  7. //创建一个新的线程
  8. Thread thread = new Thread(threadStart);
  9. //开始线程,并传入参数
  10. thread.Start("Brambling");
  11. Console.WriteLine("Main thread working...");
  12. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  13. Console.ReadKey();
  14. }
  15. }
  16. public class ThreadDemoClass
  17. {
  18. public void Run(object obj)
  19. {
  20. string name = obj as string;
  21. Console.WriteLine("Child thread working...");
  22. Console.WriteLine("My name is " + name);
  23. Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  24. }
  25. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图6
PS:这里我没有加这句代码了(thread.IsBackground = true,即把当前线程设置为后台线程),因为使用 thread.Start() 启动的线程默认为前台线程。那么前台线程和后台线程有什么区别呢?
前台线程就是系统会等待所有的前台线程运行结束后,应用程序域才会自动卸载。而设置为后台线程之后,应用程序域会在主线程执行完成时被卸载,而不会等待异步线程的执行完成。
那么上面的结果可以看到在多线程实现了参数的传递,可是它也只有一个参数呢。但是它接受的参数是 object 类型的(万类之源),也就是说既可以是值类型或引用类型,也可以是自定义类型。(当然,自定义类型其实也是属于引用类型的)下面我们使用自定义类型作为参数传递。
////

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. ThreadDemoClass demoClass = new ThreadDemoClass();
  6. //创建一个委托,并把要执行的方法作为参数传递给这个委托
  7. ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);
  8. //创建一个新的线程
  9. Thread thread = new Thread(threadStart);
  10. UserInfo userInfo = new UserInfo();
  11. userInfo.Name = "Brambling";
  12. userInfo.Age = 333;
  13. //开始线程,并传入参数
  14. thread.Start(userInfo);
  15. Console.WriteLine("Main thread working...");
  16. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  17. Console.ReadKey();
  18. }
  19. }
  20. public class ThreadDemoClass
  21. {
  22. public void Run(object obj)
  23. {
  24. UserInfo userInfo = (UserInfo)obj;
  25. Console.WriteLine("Child thread working...");
  26. Console.WriteLine("My name is " + userInfo.Name);
  27. Console.WriteLine("I'm " + userInfo.Age + " years old this year");
  28. Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  29. }
  30. }
  31. public class UserInfo
  32. {
  33. public string Name { get; set; }
  34. public int Age { get; set; }
  35. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图7
使用自定义类型作为参数传递,理论上更多个参数也都是可以实现的。

四、线程池

使用 ThreadStart 和 ParameterizedThreadStart 创建线程还是比较简单的,但是由于线程的创建和销毁需要耗费一定的开销,过多的使用线程反而会造成内存资源的浪费,从而影响性能,出于对性能的考虑,于是引入了线程池的概念。线程池并不是在 CLR 初始化的时候立刻创建线程的,而是在应用程序要创建线程来执行任务的时候,线程池才会初始化一个线程,初始化的线程和其他线程一样,但是在线程完成任务之后不会自行销毁,而是以挂起的状态回到线程池。当应用程序再次向现成池发出请求的时候,线程池里挂起的线程会再度激活执行任务。这样做可以减少线程创建和销毁所带来的开销。线程池建立的线程默认为后台线程
//

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. ThreadDemoClass demoClass = new ThreadDemoClass();
  6. //设置当没有请求时线程池维护的空闲线程数
  7. //第一个参数为辅助线程数
  8. //第二个参数为异步 I/O 线程数
  9. ThreadPool.SetMinThreads(5, 5);
  10. //设置同时处于活动状态的线程池的线程数,所有大于次数目的请求将保持排队状态,直到线程池变为可用
  11. //第一个参数为辅助线程数
  12. //第二个参数为异步 I/O 线程数
  13. ThreadPool.SetMaxThreads(100, 100);
  14. //使用委托绑定线程池要执行的方法(无参数)
  15. WaitCallback waitCallback1 = new WaitCallback(demoClass.Run1);
  16. //将方法排入队列,在线程池变为可用时执行
  17. ThreadPool.QueueUserWorkItem(waitCallback1);
  18. //使用委托绑定线程池要执行的方法(有参数)
  19. WaitCallback waitCallback2 = new WaitCallback(demoClass.Run1);
  20. //将方法排入队列,在线程池变为可用时执行
  21. ThreadPool.QueueUserWorkItem(waitCallback2,"Brambling");
  22. UserInfo userInfo = new UserInfo();
  23. userInfo.Name = "Brambling";
  24. userInfo.Age = 33;
  25. //使用委托绑定线程池要执行的方法(有参数,自定义类型的参数)
  26. WaitCallback waitCallback3 = new WaitCallback(demoClass.Run2);
  27. //将方法排入队列,在线程池变为可用时执行
  28. ThreadPool.QueueUserWorkItem(waitCallback3, userInfo);
  29. Console.WriteLine();
  30. Console.WriteLine("Main thread working...");
  31. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  32. Console.ReadKey();
  33. }
  34. }
  35. public class ThreadDemoClass
  36. {
  37. public void Run1(object obj)
  38. {
  39. string name = obj as string;
  40. Console.WriteLine();
  41. Console.WriteLine("Child thread working...");
  42. Console.WriteLine("My name is " + name);
  43. Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  44. }
  45. public void Run2(object obj)
  46. {
  47. UserInfo userInfo=(UserInfo)obj;
  48. Console.WriteLine();
  49. Console.WriteLine("Child thread working...");
  50. Console.WriteLine("My name is " + userInfo.Name);
  51. Console.WriteLine("I'm " + userInfo.Age + " years old this year");
  52. Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  53. }
  54. }
  55. public class UserInfo
  56. {
  57. public string Name { get; set; }
  58. public int Age { get; set; }
  59. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图8
使用线程池建立的线程也可以选择传递参数或不传递参数,并且参数也可以是值类型或引用类型(包括自定义类型)。看上面的结果发现了什么?没错,第一次执行的方法的线程ID为6,最后一次执行的方法的线程ID也为6。这就说明第一次请求线程池的时候,线程池建立了一个线程,当它执行完成之后就以挂起状态回到了线程池,在最后一次请求的时候,再次唤醒了该线程执行任务。这样就很容易理解了。
在这里我还发现了一个问题,就是,每次运行的时候,输出的内容的顺序都不一定是一样的。(不只是线程池,前面的也是)因为,我没有给任何线程设置优先级(线程池不能设置线程的优先级),这里其实就涉及到线程安全的问题了,很明显现在这样是非线程安全的。让我举个栗子形容一下的话,就像以前在学校下课了去吃饭一样,一拥而上,毫无秩序。
线程安全就先不说,留在后面再说(包括前面所提到的线程同步的问题),这篇博客旨在理解多线程。因为我也没有太深的理解。。。
上面我们已经实现了无参数和有参数以及自定义参数多线程的实例,可是还有一个共同的问题那就是都没有返回值。当我们用多线程做实际开发的时候大部分都是会需要返回值的,那么我们可以使用成员变量来试试。
//

  1. class Program
  2. {
  3. List<UserInfo> userInfoList = new List<UserInfo>();
  4. static void Main(string[] args)
  5. {
  6. Program program = new Program();
  7. ParameterizedThreadStart threadStart = new ParameterizedThreadStart(program.Run);
  8. Thread thread = null;
  9. UserInfo userInfo = null;
  10. for (int i = 0; i < 3; i++)
  11. {
  12. userInfo = new UserInfo();
  13. userInfo.Name = "Brambling" + i.ToString();
  14. userInfo.Age = 33 + i;
  15. thread = new Thread(threadStart);
  16. thread.Start(userInfo);
  17. thread.Join();
  18. }
  19. foreach (UserInfo user in program.userInfoList)
  20. {
  21. Console.WriteLine("My name is " + user.Name);
  22. Console.WriteLine("I'm " + user.Age + " years old this year");
  23. Console.WriteLine("Thread ID is:" + user.ThreadId);
  24. }
  25. Console.ReadKey();
  26. }
  27. public void Run(object obj)
  28. {
  29. UserInfo userInfo = (UserInfo)obj;
  30. userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
  31. userInfoList.Add(userInfo);
  32. }
  33. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图9
用上面这种方法勉强可以满足返回值的需求,但是却有很大的局限性,因为这里我使用的是成员变量,所以也就限制了线程调用的方法必须是在同一个类里面。
所以也就有了下面的方法,使用委托异步调用的方法。

五、异步委托

委托的异步调用有两个比较重要的方法:BeginInvoke()EndInvoke()

/

  1. class Program
  2. {
  3. //定义一个委托类
  4. private delegate UserInfo MyDelegate(UserInfo userInfo);
  5. static void Main(string[] args)
  6. {
  7. ThreadDemoClass demoClass = new ThreadDemoClass();
  8. List<UserInfo> userInfoList = new List<UserInfo>();
  9. UserInfo userInfo = null;
  10. UserInfo userInfoRes = null;
  11. //创建一个委托并绑定方法
  12. MyDelegate myDelegate = new MyDelegate(demoClass.Run);
  13. for (int i = 0; i < 3; i++)
  14. {
  15. userInfo = new UserInfo();
  16. userInfo.Name = "Brambling" + i.ToString();
  17. userInfo.Age = 33 + i;
  18. //传入参数并执行异步委托
  19. IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
  20. //异步操作是否完成
  21. while (!result.IsCompleted)
  22. {
  23. Thread.Sleep(100);
  24. Console.WriteLine("Main thread working...");
  25. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  26. Console.WriteLine();
  27. }
  28. //结束异步委托,并获取返回值
  29. userInfoRes = myDelegate.EndInvoke(result);
  30. userInfoList.Add(userInfoRes);
  31. }
  32. foreach (UserInfo user in userInfoList)
  33. {
  34. Console.WriteLine("My name is " + user.Name);
  35. Console.WriteLine("I'm " + user.Age + " years old this year");
  36. Console.WriteLine("Thread ID is:" + user.ThreadId);
  37. }
  38. Console.ReadKey();
  39. }
  40. }
  41. public class ThreadDemoClass
  42. {
  43. public UserInfo Run(UserInfo userInfo)
  44. {
  45. userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
  46. Console.WriteLine("Child thread working...");
  47. Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
  48. Console.WriteLine();
  49. return userInfo;
  50. }
  51. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图10
BeginInvoke() 方法用于异步委托的执行开始,EndInvoke() 方法用于结束异步委托,并获取异步委托执行完成后的返回值。IAsyncResult.IsCompleted 用于监视异步委托的执行状态(true / false),这里的时间是不定的,也就是说一定要等到异步委托执行完成之后,这个属性才会返回 true。如果异步委托的方法耗时较长,那么主线程会一直工作下去。
BeginInvoke() 是可以接受多个参数的,它的参数个数和参数类型取决于定义委托时的参数个数和类型,无论它有多少个参数,最后两个参数都是不变的,下面我们会说到。

那么我们还可以用下面的方法 WaitOne(),自定义一个等待的时间,如果在这个等待时间内异步委托没有执行完成,那么就会执行 while 里面的主线程的逻辑,反之就不会执行。
/

  1. class Program
  2. {
  3. //定义一个委托类
  4. private delegate UserInfo MyDelegate(UserInfo userInfo);
  5. static void Main(string[] args)
  6. {
  7. ThreadDemoClass demoClass = new ThreadDemoClass();
  8. List<UserInfo> userInfoList = new List<UserInfo>();
  9. UserInfo userInfo = null;
  10. UserInfo userInfoRes = null;
  11. //创建一个委托并绑定方法
  12. MyDelegate myDelegate = new MyDelegate(demoClass.Run);
  13. for (int i = 0; i < 3; i++)
  14. {
  15. userInfo = new UserInfo();
  16. userInfo.Name = "Brambling" + i.ToString();
  17. userInfo.Age = 33 + i;
  18. //传入参数并执行异步委托
  19. IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
  20. //阻止当前线程,直到 WaitHandle 收到信号,参数为指定等待的毫秒数
  21. while (!result.AsyncWaitHandle.WaitOne(1000))
  22. {
  23. Console.WriteLine("Main thread working...");
  24. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  25. Console.WriteLine();
  26. }
  27. //结束异步委托,并获取返回值
  28. userInfoRes = myDelegate.EndInvoke(result);
  29. userInfoList.Add(userInfoRes);
  30. }
  31. foreach (UserInfo user in userInfoList)
  32. {
  33. Console.WriteLine("My name is " + user.Name);
  34. Console.WriteLine("I'm " + user.Age + " years old this year");
  35. Console.WriteLine("Thread ID is:" + user.ThreadId);
  36. }
  37. Console.ReadKey();
  38. }
  39. }
  40. public class ThreadDemoClass
  41. {
  42. public UserInfo Run(UserInfo userInfo)
  43. {
  44. userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
  45. Console.WriteLine("Child thread working...");
  46. Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
  47. Console.WriteLine();
  48. return userInfo;
  49. }
  50. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图11
WaitOne() 方法只能用于监视当前线程的对象,如果要监视多个对象可以使用 WaitAny(WaitHandle[], int)WaitAll (WaitHandle[] , int) 这两个方法。
/

  1. class Program
  2. {
  3. //定义一个委托类
  4. private delegate UserInfo MyDelegate(UserInfo userInfo);
  5. static void Main(string[] args)
  6. {
  7. ThreadDemoClass demoClass = new ThreadDemoClass();
  8. List<UserInfo> userInfoList = new List<UserInfo>();
  9. UserInfo userInfo = null;
  10. UserInfo userInfoRes = null;
  11. //创建一个委托并绑定方法
  12. MyDelegate myDelegate = new MyDelegate(demoClass.Run);
  13. for (int i = 0; i < 3; i++)
  14. {
  15. userInfo = new UserInfo();
  16. userInfo.Name = "Brambling" + i.ToString();
  17. userInfo.Age = 33 + i;
  18. //传入参数并执行异步委托
  19. IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
  20. IAsyncResult result1 = myDelegate.BeginInvoke(userInfo, null, null);
  21. //定义要监视的对象,不能包含对同一对象的多个引用
  22. WaitHandle[] waitHandles = new WaitHandle[] { result.AsyncWaitHandle, result1.AsyncWaitHandle };
  23. while (!WaitHandle.WaitAll(waitHandles,1000))
  24. {
  25. Console.WriteLine("Main thread working...");
  26. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  27. Console.WriteLine();
  28. }
  29. //结束异步委托,并获取返回值
  30. userInfoRes = myDelegate.EndInvoke(result);
  31. userInfoList.Add(userInfoRes);
  32. userInfoRes = myDelegate.EndInvoke(result1);
  33. userInfoList.Add(userInfoRes);
  34. }
  35. foreach (UserInfo user in userInfoList)
  36. {
  37. Console.WriteLine("My name is " + user.Name);
  38. Console.WriteLine("I'm " + user.Age + " years old this year");
  39. Console.WriteLine("Thread ID is:" + user.ThreadId);
  40. }
  41. Console.ReadKey();
  42. }
  43. }
  44. public class ThreadDemoClass
  45. {
  46. public UserInfo Run(UserInfo userInfo)
  47. {
  48. userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
  49. Console.WriteLine("Child thread working...");
  50. Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
  51. Console.WriteLine();
  52. return userInfo;
  53. }
  54. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图12
WaitAll() 方法和 WaitAny() 方法都可以监视多个对象,不同的是 WaitAll() 方法需要等待所有的监视对象都收到信号之后才会返回 true,否则返回 false。而 WaitAny() 则是当有一个监视对象收到信号之后就会返回一个 int 值,这个 int 值代表的是当前收到信号的监视对象的索引。注意:在定义监视对象的时候,不能包含对同一个对象的多个引用,我这里是定义的两个示例,所以是不同的对象。
接下来我们看上面的执行结果。咦?为什么主线程没有“工作”呢?
这里你可以在异步委托调用的 Run() 方法里面为线程设置一个几秒钟或者更长的挂起时间。然后在设置监视对象这里设置一个小于线程挂起的时间,然后调试你就能发现问题的所在了。其实也不算是问题,只是之前的理解有误。
其实 WaitAll() 方法和 WaitAny() 方法设置监视对象,然后指定一个时间(毫秒值),这里的意思是当所有的监视对象在指定的时间内都接收到信号时(这里是指 WaitAll() 方法),就不会执行 while 里面的主线程的工作,反之就会执行。
这里你可能会有疑问,如果是这样,那我把前面的逻辑非运算符去掉那不就相反了么。这么理解逻辑上是没错的,但是我还是要说的是,尽量不要去尝试,因为这会是个死循环。WaitAny() 这个方法也是一样的理解,不同的是,它不需要等到所有的监视对象都收到信号,它只需要一个监视对象收到信号就够了,这里就不在演示了。

上面的方法可以看出,我们虽然使用的是异步的方式调用的方法,但是依旧需要等待异步的方法返回执行的结果,尽管我们可以不阻塞主线程,但是还是觉得不太方便。所以也就有了本篇博客最后一个要点,异步委托的回调函数
/////

  1. class Program
  2. {
  3. //定义一个委托类
  4. private delegate UserInfo MyDelegate(UserInfo userInfo);static void Main(string[] args)
  5. {
  6. ThreadDemoClass demoClass = new ThreadDemoClass();
  7. UserInfo userInfo = null;
  8. //创建一个委托并绑定方法
  9. MyDelegate myDelegate = new MyDelegate(demoClass.Run);
  10. //创建一个回调函数的委托
  11. AsyncCallback asyncCallback = new AsyncCallback(Complete);
  12. for (int i = 0; i < 3; i++)
  13. {
  14. userInfo = new UserInfo();
  15. userInfo.Name = "Brambling" + i.ToString();
  16. userInfo.Age = 33 + i;
  17. //传入参数并执行异步委托,并设置回调函数
  18. IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, null);
  19. }
  20. Console.WriteLine("Main thread working...");
  21. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  22. Console.WriteLine();
  23. Console.ReadKey();
  24. }
  25. public static void Complete(IAsyncResult result)
  26. {
  27. UserInfo userInfoRes = null;
  28. AsyncResult asyncResult = (AsyncResult)result;
  29. //获取在其上调用异步调用的委托对象
  30. MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate;
  31. //结束在其上调用的异步委托,并获取返回值
  32. userInfoRes = myDelegate.EndInvoke(result);
  33. Console.WriteLine("My name is " + userInfoRes.Name);
  34. Console.WriteLine("I'm " + userInfoRes.Age + " years old this year");
  35. Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId);
  36. }
  37. }
  38. public class ThreadDemoClass
  39. {
  40. public UserInfo Run(UserInfo userInfo)
  41. {
  42. userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
  43. Console.WriteLine("Child thread working...");
  44. Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
  45. Console.WriteLine();
  46. return userInfo;
  47. }
  48. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图13
从上面可以看到主线程再执行了异步委托之后继续执行了下去,然后在回调函数里输出了信息,也就是说在调用了异步委托之后就不管了,把之后的结束委托和获取委托的返回值放到了回调函数中,因为回调函数是没有返回值的,但是回调函数可以有一个参数。上面说到的 BeginInvoke() 方法的最后两个参数,它的倒数第二个参数就是一个回调函数的委托,最后一个参数可以设置传入回调函数的参数。如下:
//

  1. class Program
  2. {
  3. //定义一个委托类
  4. private delegate UserInfo MyDelegate(UserInfo userInfo);
  5. static List<UserInfo> userInfoList = new List<UserInfo>();
  6. static void Main(string[] args)
  7. {
  8. ThreadDemoClass demoClass = new ThreadDemoClass();
  9. UserInfo userInfo = null;
  10. //创建一个委托并绑定方法
  11. MyDelegate myDelegate = new MyDelegate(demoClass.Run);
  12. //创建一个回调函数的委托
  13. AsyncCallback asyncCallback = new AsyncCallback(Complete);
  14. //回调函数的参数
  15. string str = "I'm the parameter of the callback function!";
  16. for (int i = 0; i < 3; i++)
  17. {
  18. userInfo = new UserInfo();
  19. userInfo.Name = "Brambling" + i.ToString();
  20. userInfo.Age = 33 + i;
  21. //传入参数并执行异步委托,并设置回调函数
  22. IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, str);
  23. }
  24. Console.WriteLine("Main thread working...");
  25. Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
  26. Console.WriteLine();
  27. Console.ReadKey();
  28. }
  29. public static void Complete(IAsyncResult result)
  30. {
  31. UserInfo userInfoRes = null;
  32. AsyncResult asyncResult = (AsyncResult)result;
  33. //获取在其上调用异步调用的委托对象
  34. MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate;
  35. //结束在其上调用的异步委托,并获取返回值
  36. userInfoRes = myDelegate.EndInvoke(result);
  37. Console.WriteLine("My name is " + userInfoRes.Name);
  38. Console.WriteLine("I'm " + userInfoRes.Age + " years old this year");
  39. Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId);
  40. //获取回调函数的参数
  41. string str = result.AsyncState as string;
  42. Console.WriteLine(str);
  43. }
  44. }
  45. public class ThreadDemoClass
  46. {
  47. public UserInfo Run(UserInfo userInfo)
  48. {
  49. userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
  50. Console.WriteLine("Child thread working...");
  51. Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
  52. Console.WriteLine();
  53. return userInfo;
  54. }
  55. }

执行结果:
C# 多线程编程第一步——理解多线程 - 图14
回调函数的参数也是 object 类型的,我这里用的是一个 string 类型,但是它也可以是自定义类型的参数。
本篇博客到此结束,在写这篇博客的同时也让我个人对多线程编程加深了理解,多线程编程的知识点还有很多,后面再继续与大家分享。