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 }

异步方式出结果,如下所示:

通过对以上示例进行分析,得出结论如下:

  1. 异步方式不是顺序执行,即具有无序性。
  2. 异步方式采用多线程方式,和UI不是同一个线程,所以前端UI不会卡住。
  3. 异步多线程方式执行时间短,响应速度快。

通过观察任务管理器,发现同步方式比较耗时间,异步方式比较耗资源【本例是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 }

异步回调执行示例,如下所示:

 通过对异步回调方式执行结果进行分析,结论如下所示:

  1. 通过观察线程ID可以发现,由于对循环计算的功能进行了封装,为一个独立的函数,所以在Action通过BeginInvoke发起时,又是一个新的线程。
  2. 通过async和await在通过Task.Run方式返回时,也会重新生成新的线程。
  3. 通过回调函数,可以保证异步线程的执行顺序。
  4. 通过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方法的示例进行分析,得出结论如下所示:

  1. 在主线程中调用EndInvoke,会进行阻塞,前端页面卡死。
  2. Func的返回值是泛型类型,可以返回任意类型的值。

异步多线程返回值回调

以上就是通过Action和Func的对应BeginInvoke方法实现异步多线程的示例,旨在抛砖引玉,大家共同学习,一起进步。

菩萨蛮·书江西造口壁

【作者】辛弃疾 【朝代】宋

郁孤台下清江水,中间多少行人泪?西北望长安,可怜无数山。

青山遮不住,毕竟东流去。江晚正愁余,山深闻鹧鸪。

出处:https://www.cnblogs.com/hsiang/p/15676795.html

C