时间:2022-04-05 09:54:52 | 栏目:.NET代码 | 点击:次
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。本文以一些简单的小例子,简述如何将程序由同步方式,一步一步演变成异步多线程方式,仅供学习分享使用,如有不足之处,还请指正。
业务场景:用户点击一个按钮,然后做一个耗时的业务。同步方式代码如下所示:
private void btnSync_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnSync_Click同步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); for (int i = 0; i < 5; i++) { string name = string.Format("{0}_{1}", "btnSync_Click", i); this.DoSomethingLong(name); } Console.WriteLine("************btnSync_Click同步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); } /// <summary> /// 模拟做一些长时间的工作 /// </summary> /// <param name="name"></param> private void DoSomethingLong(string name) { Console.WriteLine("************DoSomethingLong 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff")); //CPU计算累加和 long rest = 0; for (int i = 0; i < 1000000000; i++) { rest += i; } Console.WriteLine("************DoSomethingLong 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest); }
同步方式输出结果,如下所示:
通过对以上示例进行分析,得出结论如下:
如何优化同步方式存在的问题呢?答案是由同步方式改为异步异步多线程方式。代码如下所示:
private void btnAsync_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync_Click异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); Action<string> action = new Action<string>(DoSomethingLong); for (int i = 0; i < 5; i++) { string name = string.Format("{0}_{1}", "btnAsync_Click", i); action.BeginInvoke(name,null,null); } Console.WriteLine("************btnAsync_Click异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); }
异步方式出结果,如下所示:
通过对以上示例进行分析,得出结论如下:
通过观察任务管理器,发现同步方式比较耗时间,异步方式比较耗资源【本例是CPU密集型操作】,属于以资源换性能。同步方式和异步方式的CPU利用率,如下图所示:
通过上述例子,发现由于采用异步的原因,线程还未结束,但是排在后面的语句就先执行,所以统计的程序执行总耗时为0秒。为了优化此问题,采用async与await组合方式执行,代码如下所示:
private async void btnAsync2_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync_Click2异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); await DoAsync(); Console.WriteLine("************btnAsync_Click2异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); } /// <summary> /// 异步方法 /// </summary> /// <returns></returns> private async Task DoAsync() { Action<string> action = new Action<string>(DoSomethingLong); List<IAsyncResult> results = new List<IAsyncResult>(); for (int i = 0; i < 5; i++) { string name = string.Format("{0}_{1}", "btnAsync_Click", i); IAsyncResult result = action.BeginInvoke(name, null, null); results.Add(result); } await Task.Run(()=> { while (true) { for (int i = 0; i < results.Count; i++) { var result = results[i]; if (result.IsCompleted) { results.Remove(result); break; } } if (results.Count < 1) { break; } Thread.Sleep(200); } }); }
经过优化,执行结果如下所示:
通过异步多线程优化后的执行结果,进行分析后得出的结论如下:
通过以上方式,采用异步多线程的方式,共耗时3.26秒,比同步方式的9.32秒,提高了2.85倍,并非线性增加。且每次执行的总耗时会上下浮动,并非固定值。
上述async与await组合,是一种实现异步调用的方式,其实Action本身也具有回调函数【AsyncCallback】,通过回调函数一样可以实现对应功能。具体如下所示:
/// <summary> /// 异步回调 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnAsync3_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync_Click3异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); Action action = DoAsync3; AsyncCallback asyncCallback = new AsyncCallback((ar) => { if (ar.IsCompleted) { Console.WriteLine("************btnAsync_Click3异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); } }); action.BeginInvoke(asyncCallback, null); } private void DoAsync3() { Action<string> action = new Action<string>(DoSomethingLong); List<IAsyncResult> results = new List<IAsyncResult>(); for (int i = 0; i < 5; i++) { string name = string.Format("{0}_{1}", "btnAsync_Click3", i); IAsyncResult result = action.BeginInvoke(name, null, null); results.Add(result); } while (true) { for (int i = 0; i < results.Count; i++) { var result = results[i]; if (result.IsCompleted) { results.Remove(result); break; } } if (results.Count < 1) { break; } Thread.Sleep(200); } }
异步回调执行示例,如下所示:
通过对异步回调方式执行结果进行分析,结论如下所示:
信号量方式是通过BeginInvoke返回值IAsyncResult中的异步等待AsyncWaitHandle触发信号WaitOne,可以实现信号的实时响应,具体代码如下:
private void btnAsync4_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync_Click4异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); Action action = DoAsync3; var asyncResult = action.BeginInvoke(null, null); //此处中间可以做其他的工作,然后在最后等待线程的完成 asyncResult.AsyncWaitHandle.WaitOne(); Console.WriteLine("************btnAsync_Click4异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); }
信号量示例截图如下所示:
通过对异步信号量方式的测试结果进行分析,得出结论如下:
上述示例的委托都是无返回值类型的,那么对于有返回值的函数,如何获取呢?答案就是采用Func。示例如下所示:
private void btnAsync5_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync5_Click异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); string name = string.Format("{0}_{1}", "btnAsync_Click5", 0); Func<string, int> func = new Func<string, int>(DoSomethingLongAndReturn); IAsyncResult asyncResult = func.BeginInvoke(name, null, null); //此处中间可以做其他的工作,然后在最后等待线程的完成 int result = func.EndInvoke(asyncResult); Console.WriteLine("************btnAsync5_Click异步方法 结束,线程ID= {0},返回值={1}************", Thread.CurrentThread.ManagedThreadId,result); watch.Stop(); Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); } private int DoSomethingLongAndReturn(string name) { Console.WriteLine("************DoSomethingLong 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff")); //CPU计算累加和 long rest = 0; for (int i = 0; i < 1000000000; i++) { rest += i; } Console.WriteLine("************DoSomethingLong 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest); return DateTime.Now.Day; }
采用Func方式的EndInvoke,可以获取返回值,示例如下:
通过对Func方式的EndInvoke方法的示例进行分析,得出结论如下所示:
为了解决以上获取返回值时,前端页面卡死的问题,可以采用回调函数进行解决,如下所示:
private void btnAsync6_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync6_Click异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId); string name = string.Format("{0}_{1}", "btnAsync_Click6", 0); Func<string, int> func = new Func<string, int>(DoSomethingLongAndReturn); AsyncCallback callback = new AsyncCallback((asyncResult) => { int result = func.EndInvoke(asyncResult); Console.WriteLine("************btnAsync6_Click异步方法 结束,线程ID= {0},返回值={1}************", Thread.CurrentThread.ManagedThreadId, result); watch.Stop(); Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); }); func.BeginInvoke(name, callback, null); }
采用回调方式,示例截图如下:
通过对回调方式的示例进行分析,得出结论如下: