异步多线程 :
await/async是一个语法糖,不是一个全新的异步多线程的使用方式。它本身不会产生新的线程,它是依托于Task存在,所以在实际程序运行过程中是有多线程的,这些多线程是有Task产生的。
如果一个方法没有返回值,可以用下面方法写,下面中的2个方法是等价的:
public async void NoReturn() { Console.WriteLine($"This is NoReturn Start {Thread.CurrentThread.ManagedThreadId}"); await Task.Run(() => { Console.WriteLine($"This is NoReturn Task Start {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(2000); Console.WriteLine($"This is NoReturn Task End {Thread.CurrentThread.ManagedThreadId}"); }); Console.WriteLine($"This is NoReturn End {Thread.CurrentThread.ManagedThreadId}"); } public async Task ReturnTask() { Console.WriteLine($"This is NoReturn Start {Thread.CurrentThread.ManagedThreadId}"); await Task.Run(() => { Console.WriteLine($"This is NoReturn Task Start {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(2000); Console.WriteLine($"This is NoReturn Task End {Thread.CurrentThread.ManagedThreadId}"); }); Console.WriteLine($"This is NoReturn End {Thread.CurrentThread.ManagedThreadId}"); }
有没有await关键字其实是有区别的,
代码:
public class AwaitAsyncClassNew { public void Show() { Console.WriteLine($"This is Main Start {Thread.CurrentThread.ManagedThreadId}"); this.NoReturn(); Console.WriteLine($"This is Main Start {Thread.CurrentThread.ManagedThreadId}"); } #region 无返回值 public void NoReturn() { Console.WriteLine($"This is NoReturn Start {Thread.CurrentThread.ManagedThreadId}"); Task.Run(() => { Console.WriteLine($"This is NoReturnTask Start {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"This is NoReturnTask End {Thread.CurrentThread.ManagedThreadId}"); }); Console.WriteLine($"This is NoReturn End {Thread.CurrentThread.ManagedThreadId}"); } public async Task ReturnTask() { Console.WriteLine($"This is NoReturn Start {Thread.CurrentThread.ManagedThreadId}"); await Task.Run(() => { Console.WriteLine($"This is NoReturnTask Start {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"This is NoReturnTask End {Thread.CurrentThread.ManagedThreadId}"); }); Console.WriteLine($"This is NoReturn End {Thread.CurrentThread.ManagedThreadId}"); } #endregion }
此时show方法调用的是NoReturn方法,结果如下:
This is Main Start 1 This is NoReturn Start 1 This is NoReturn End 1 This is Main End 1 This is NoReturnTask Start 4 This is NoReturnTask End 4
从这个结果我们可以看出Task启用的是一个子线程4来运行方法,后续的操作都是主线程1来完成的。
代码:现在调用ReturnTask方法
public class AwaitAsyncClassNew { public void Show() { Console.WriteLine($"This is Main Start {Thread.CurrentThread.ManagedThreadId}"); this.ReturnTask(); Console.WriteLine($"This is Main End {Thread.CurrentThread.ManagedThreadId}"); } #region 无返回值 public void NoReturn() { Console.WriteLine($"This is NoReturn Start {Thread.CurrentThread.ManagedThreadId}"); Task.Run(() => { Console.WriteLine($"This is NoReturnTask Start {Thread.CurrentThread.ManagedThreadId}"); // Thread.Sleep(200); Console.WriteLine($"This is NoReturnTask End {Thread.CurrentThread.ManagedThreadId}"); }); Console.WriteLine($"This is NoReturn End {Thread.CurrentThread.ManagedThreadId}"); } public async Task ReturnTask() { Console.WriteLine($"This is NoReturn Start {Thread.CurrentThread.ManagedThreadId}");//由调用线程执行 await Task.Run(() => { //内部操作由Task的子线程完成 Console.WriteLine($"This is NoReturnTask Start {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"This is NoReturnTask End {Thread.CurrentThread.ManagedThreadId}"); }); //由调用线程执行,启动一个新线程来执行内部操作,然后回去干自己的事 Console.WriteLine($"This is NoReturn End {Thread.CurrentThread.ManagedThreadId}");////由Task的子线程完成。 } #endregion }
结果:
This is Main Start 1 This is NoReturn Start 1 This is NoReturnTask Start 4 This is NoReturnTask End 4 This is Main End 1 This is NoReturn End 4
加了await关键字之后执行结果就不一样了,在ReturnTask中执行到了await代码处之后就会跳出这个方法,直接继续执行,ReturnTask中的后续代码就会由Task的子线程来完成执行。
我们把NoReturn换一种写法:
public void NoReturn() { Console.WriteLine($"This is NoReturn Start {Thread.CurrentThread.ManagedThreadId}"); Task task = Task.Run(() => { Console.WriteLine($"This is NoReturnTask Start {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"This is NoReturnTask End {Thread.CurrentThread.ManagedThreadId}"); }); task.ContinueWith(t => Console.WriteLine($"This is NoReturn End {Thread.CurrentThread.ManagedThreadId}")); // Console.WriteLine($"This is NoReturn End {Thread.CurrentThread.ManagedThreadId}"); }
结果:
This is Main Start 1 This is NoReturn Start 1 This is Main End 1 This is NoReturnTask Start 4 This is NoReturnTask End 4 This is NoReturn End 5
首先我们要清楚ContinueWith这个方法是一个回调方法,是Task启动线程完成任务之后执行的方法。从这结果我们可以看出,This is NoReturn End 5不是由调用线程(如果不是多个线程的话,可以认为是主线程)执行的,而是Task的子线程执行的,其实结果和ReturnTask方法一样,都不是调用线程执行的,所以:可以认为执行完await这行代码之后的代码都是Task启动的子线程来完成的,也就是说将后面的代码包装成了一个回调方法。但是一定要注意的是执行这个回调方法的线程是有多种可能性的,很可能回调方法就是启动的那个子线程来执行,但也可能Task分配一个新的线程来完成这个后续操作,比如这里就是新分配了线程5来完成的。其实在前面说过的防止界面不卡,文件上传进度这些问题也都可以用这种形式来解决。
多个await:
public async Task ReturnTask() { Console.WriteLine($"This is NoReturn Start {Thread.CurrentThread.ManagedThreadId}");//由调用线程执行 Task task = Task.Run(() => { //内部操作由Task的子线程完成 Console.WriteLine($"This is NoReturnTask Start {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"This is NoReturnTask End {Thread.CurrentThread.ManagedThreadId}"); }); //由调用线程执行,启动一个新线程来执行内部操作 await task; #region 这块区域可以看做是task的回调方法 Console.WriteLine($"This is NoReturn End {Thread.CurrentThread.ManagedThreadId}");////由Task的子线程完成。 Task task2 = Task.Run(() => { //内部操作由Task的子线程完成 Console.WriteLine($"This is NoReturnTask2 Start {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"This is NoReturnTask2 End {Thread.CurrentThread.ManagedThreadId}"); }); //由调用线程执行,启动一个新线程来执行内部操作 await task2;//这行代码是由task1开启的子线程执行的,执行完这一行代码之后就返回,后续代码就是task2开启的子线程执行的了。 #region task2的回调方法 Console.WriteLine($"This is NoReturn2 End {Thread.CurrentThread.ManagedThreadId}");////由Task的子线程完成。 #endregion #endregion }
结果:
This is Main Start 1 This is NoReturn Start 1 This is NoReturnTask Start 4 This is NoReturnTask End 4 This is Main End 1 This is NoReturn End 4 This is NoReturnTask2 Start 5 This is NoReturnTask2 End 5 This is NoReturn2 End 5
从结果就可以看到NoReturn2和NoReturnTask2都是由线程5执行的,但是由于执行回调方法的不确定性,所以多次调用的话NoReturn2可能就是另一个线程来执行回调了。我们常说的用同步编码的方式来写异步就是用await/async这种形式来写。因为这种形式在使用await之后当前线程可以继续执行后续代码,task开启的子线程也可以继续执行其他方法。
有返回值的写法:
#region 有返回值 public async Task<long> ReturnLongAwait() { Console.WriteLine($"This is ReturnLongAwait Start {Thread.CurrentThread.ManagedThreadId}"); long result = 0; Task<long> task = Task.Run(() => { Console.WriteLine($"This is ReturnLongAwait Task Start {Thread.CurrentThread.ManagedThreadId}"); for (int i = 0; i < 1000000; i++) { result += i; } Console.WriteLine($"This is ReturnLongAwait Task End {Thread.CurrentThread.ManagedThreadId}"); return result; }); await task; Console.WriteLine($"This is ReturnLongAwait End {Thread.CurrentThread.ManagedThreadId}"); return result; } public long ReturnLong() { Console.WriteLine($"This is ReturnLong Start {Thread.CurrentThread.ManagedThreadId}"); long result = 0; Task.Run(() => { Console.WriteLine($"This is ReturnLong Task Start {Thread.CurrentThread.ManagedThreadId}"); for (int i = 0; i < 1000000; i++) { result += i; } Console.WriteLine($"This is ReturnLong Task End {Thread.CurrentThread.ManagedThreadId}"); return result; }); Console.WriteLine($"This is ReturnLong End {Thread.CurrentThread.ManagedThreadId}"); return result; } #endregion