0. 摘要
- 为探究async、await对程序执行的影响,本文从被async修饰的方法的方法体中是否调用新线程以及是否使用await调用异步方法这两个条件来进行分析,在四种情况下得出await提高线程利用率的结论
1. 实验
1.1 实验准备
代码准备:准备两个耗时方法Func1和Func2,两个方法都由async修饰且功能相似,都是进行一个没有意义耗时的操作。但区别是Func2中方法的实现是通过Task.Run()来执行的。
代码说明:Thread.CurrentThread.ManagedThreadId可获得当前正在使用的线程的Id,运行结果中不同的数字代表不同的线程
-
async Task<decimal> Func1(int n) { Console.WriteLine("Func1_start: " + Thread.CurrentThread.ManagedThreadId); decimal result = 1; Random random = new Random(); for (int i = 0; i < n * n; i++) { result += (decimal)random.NextDouble(); Console.WriteLine("Func1_end: " + Thread.CurrentThread.ManagedThreadId); return result; } async Task<decimal> Func2(int n) { Console.WriteLine("Func2_start: " + Thread.CurrentThread.ManagedThreadId); return await Task.Run(() => { decimal result = 1; Random random = new Random(); for (int i = 0; i < n * n; i++) { result += (decimal)random.NextDouble(); } Console.WriteLine("Func2_end: " + Thread.CurrentThread.ManagedThreadId); return result; }); }
1.2 实验开始
(1)对于方法体内没有调用新线程的异步方法Func1(仅由async声明的异步方法)
- a. 不用await调用
-
Console.WriteLine("Main:" + Thread.CurrentThread.ManagedThreadId); Func1(10000); Console.WriteLine("Main:" + Thread.CurrentThread.ManagedThreadId);
- 运行结果
Main:1
Func1_start: 1
Func1_end: 1
Main:1
-
- b. 使用await调用
-
Console.WriteLine("Main:" + Thread.CurrentThread.ManagedThreadId); await Func1(10000); Console.WriteLine("Main:" + Thread.CurrentThread.ManagedThreadId);
- 运行结果
Main:1
Func1_start: 1
Func1_end: 1
Main:1
-
- 分析:当方法体内没有调用新线程的方法时,即便被asycn修饰,仍会同步执行,不会实现异步的效果;且无论是否使用await调用都是同步执行,不会有变化。
(2)对于方法体内有调用新线程的异步方法Func2(如Task.Run() 或 Task.Factory.StartNew() 等)
- a. 不用await调用
-
Console.WriteLine("Main:" + Thread.CurrentThread.ManagedThreadId); Func2(10000); Console.WriteLine("Main:" + Thread.CurrentThread.ManagedThreadId);
- 运行结果1
Main:1
Func2_start: 1
Main:1 -
Console.WriteLine("Main:" + Thread.CurrentThread.ManagedThreadId); Func2(10); Console.WriteLine("Main:" + Thread.CurrentThread.ManagedThreadId);
- 运行结果2(为了让Fun2快点结束,把参数设置的很小)
Main:1
Func2_start: 1
Main:1
Func2_end: 4 - 分析:
- 从结果1可以看出方法Func2还没执行完,主线程Main-1就继续执行了。因此,可得出方法体内有Task.Run()的异步方法可以实现异步效果;
- 把Func2的参数值设小一点让Fun2快点结束,从结果2可以看到执行此异步方法的线程是另一个线程Func2_end:4
- 也可看出不使用await的情况下主线程并没有切换
- 说明在执行异步方法的时候,主线程是一直被阻塞,直到异步方法执行结束,才继续执行后面的代码
- 此外,之后的代码如果需要异步方法Func2的的执行结果,还可能会报错(因为Func2还没执行结束,主程序那不到Func2的结果)
-
- b. 用await调用
-
Console.WriteLine("\n"); await Func2(10000); Console.WriteLine("Main:" + Thread.CurrentThread.ManagedThreadId);
- 运行结果:下面是执行了多次的结果,基本就是下面两种结果
//结果1
Main:1
Func2_start: 1
Func2_end: 5
Main:5//结果2
Main:1
Func2_start: 1
Func2_end: 4
Main:4 - 分析:
- 从结果可以看出主线程已经发生了变化,说明是异步执行的。
- 由于主线程切换,说明最开始使用的线程Main-1已经被放回线程池,等别的任务需要用到时线程Main-1时就可以去执行了,而不会因异步方法的执行而阻塞,提高了线程的利用率。
- 与不使用await调用方法对比,使用await调用异步方法会等此异步方法结束后才执行之后的代码。
- 执行异步方法的线程是新线程,这个新线程执行完异步方法后会接着执行之后主程序中的代码。
-
2. 结论
- 在async声明的方法中,如果要实现异步,还需要方法体中调Task.Run (或者其他可以调用新线程的方法,如Task.Factory.StartNew()等)。
- await 仅对用async声明且内部调用了新线程的异步方法起到作用,并会以同步的方式调用异步的方法;因此,想要真正的利用起来awiat,需要对内部有Task.Run的异步方法进行调用。
- 用await调用由async声明且内部调用了新线程的异步方法,执行流程是:
- 线程A执行主程序代码→执行到await 异步方法()中的Task.Run时,线程A回到线程池,由线程B来执行此异步方法→异步方法执行功能结束后,线程B不释放,而是直接执行主程序后面的代码
4.合理的使用async、await可以提高线程的利用率,避免线程阻塞,提高系统的并发量。