C# 多线程入门系列(一)
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。本文以一些简单的小例子,简述如何将程序由同步方式,一步一步演变成异步多线程方式,仅供学习分享使用,如有不足之处,还请指正。
同步方式
如何优化同步方式存在的问题呢?答案是由同步方式改为异步异步多线程方式。代码如下所示:
1 private void btnAsync_Click(object sender, EventArgs e) 2 { 3 Stopwatch watch = Stopwatch.StartNew(); 4 watch.Start(); 5 Console.WriteLine("************btnAsync_Click异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); 6 Action<string> action = new Action<string>(DoSomethingLong); 7 for (int i = 0; i < 5; i++) 8 { 9 string name = string.Format("{0}_{1}", "btnAsync_Click", i); 10 action.BeginInvoke(name,null,null); 11 } 12 Console.WriteLine("************btnAsync_Click异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); 13 watch.Stop(); 14 Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); 15 }
异步方式出结果,如下所示:
通过对以上示例进行分析,得出结论如下:
- 异步方式不是顺序执行,即具有无序性。
- 异步方式采用多线程方式,和UI不是同一个线程,所以前端UI不会卡住。
- 异步多线程方式执行时间短,响应速度快。
通过观察任务管理器,发现同步方式比较耗时间,异步方式比较耗资源【本例是CPU密集型操作】,属于以资源换性能。同步方式和异步方式的CPU利用率,如下图所示:
异步多线程优化
上述async与await组合,是一种实现异步调用的方式,其实Action本身也具有回调函数【AsyncCallback】,通过回调函数一样可以实现对应功能。具体如下所示:
1 ///2 /// 异步回调 3 /// 4 /// 5 /// 6 private void btnAsync3_Click(object sender, EventArgs e) 7 { 8 Stopwatch watch = Stopwatch.StartNew(); 9 watch.Start(); 10 Console.WriteLine("************btnAsync_Click3异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); 11 Action action = DoAsync3; 12 AsyncCallback asyncCallback = new AsyncCallback((ar) => 13 { 14 if (ar.IsCompleted) 15 { 16 Console.WriteLine("************btnAsync_Click3异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); 17 watch.Stop(); 18 Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); 19 } 20 }); 21 action.BeginInvoke(asyncCallback, null); 22 23 } 24 25 private void DoAsync3() 26 { 27 Action<string> action = new Action<string>(DoSomethingLong); 28 List results = new List (); 29 for (int i = 0; i < 5; i++) 30 { 31 string name = string.Format("{0}_{1}", "btnAsync_Click3", i); 32 IAsyncResult result = action.BeginInvoke(name, null, null); 33 results.Add(result); 34 } 35 36 while (true) 37 { 38 for (int i = 0; i < results.Count; i++) 39 { 40 var result = results[i]; 41 if (result.IsCompleted) 42 { 43 results.Remove(result); 44 break; 45 } 46 } 47 if (results.Count < 1) 48 { 49 break; 50 } 51 Thread.Sleep(200); 52 53 } 54 }
异步回调执行示例,如下所示:
通过对异步回调方式执行结果进行分析,结论如下所示:
- 通过观察线程ID可以发现,由于对循环计算的功能进行了封装,为一个独立的函数,所以在Action通过BeginInvoke发起时,又是一个新的线程。
- 通过async和await在通过Task.Run方式返回时,也会重新生成新的线程。
- 通过回调函数,可以保证异步线程的执行顺序。
- 通过Thread.Sleep(200)的方式进行等待,会有一定时间范围延迟。
异步信号量
上述示例的委托都是无返回值类型的,那么对于有返回值的函数,如何获取呢?答案就是采用Func。示例如下所示:
1 private void btnAsync5_Click(object sender, EventArgs e) 2 { 3 Stopwatch watch = Stopwatch.StartNew(); 4 watch.Start(); 5 Console.WriteLine("************btnAsync5_Click异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); 6 string name = string.Format("{0}_{1}", "btnAsync_Click5", 0); 7 Func<string, int> func = new Func<string, int>(DoSomethingLongAndReturn); 8 IAsyncResult asyncResult = func.BeginInvoke(name, null, null); 9 //此处中间可以做其他的工作,然后在最后等待线程的完成 10 int result = func.EndInvoke(asyncResult); 11 Console.WriteLine("************btnAsync5_Click异步方法 结束,线程ID= {0},返回值={1}************", Thread.CurrentThread.ManagedThreadId,result); 12 watch.Stop(); 13 Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); 14 } 15 16 private int DoSomethingLongAndReturn(string name) 17 { 18 Console.WriteLine("************DoSomethingLong 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff")); 19 //CPU计算累加和 20 long rest = 0; 21 for (int i = 0; i < 1000000000; i++) 22 { 23 rest += i; 24 } 25 Console.WriteLine("************DoSomethingLong 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest); 26 return DateTime.Now.Day; 27 }
采用Func方式的EndInvoke,可以获取返回值,示例如下:
通过对Func方式的EndInvoke方法的示例进行分析,得出结论如下所示:
- 在主线程中调用EndInvoke,会进行阻塞,前端页面卡死。
- Func的返回值是泛型类型,可以返回任意类型的值。
异步多线程返回值回调
以上就是通过Action和Func的对应BeginInvoke方法实现异步多线程的示例,旨在抛砖引玉,大家共同学习,一起进步。
菩萨蛮·书江西造口壁
【作者】辛弃疾
郁孤台下清江水,中间多少行人泪?西北望长安,可怜无数山。
青山遮不住,毕竟东流去。江晚正愁余,山深闻鹧鸪。
出处:https://www.cnblogs.com/hsiang/p/15676795.html