今天要记录的内容摘要是:
什么时候异步代码能“等”在那里,什么时候不会“等”
这两天Coding的时候碰到一个事儿,就是想让异步等在那里结果却直接执行过去了,比如这样:
1 async static void Count() 2 { 3 Console.WriteLine("Task Void 开始"); 4 Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 5 int count = 0; 6 while (count < 10) 7 { 8 Console.WriteLine("Print value:{0}", count); 9 count++; 10 await Task.Delay(500); 11 } 12 Console.WriteLine("Task Void 结束"); 13 }
接下来写这两句:
1 var taskVoid = new Task(Count); 2 await taskVoid;
哈哈,原本按照其它异步方式去写的时候比如 await HttpClient.GetAsync(); 是非常正常的,程序的逻辑会停在这一句,直到获得结果才会进行下面的语句。但运行 await taskVoid 后,会立即运行下面的语句,这是怎么回事?经过仔细检查发现是因为 Count 方法没有返回值,是 void ,因此必须要加以改造。如果 Count 方法返回值是 Task ,await 才能起效,比如改成这样:
1 static void Main(string[] args) 2 { 3 Entry(); 4 Console.ReadLine(); 5 6 } 7 8 async static Task CountAsync() 9 { 10 Console.WriteLine("Task Void 开始"); 11 int count = 0; 12 while (count < 10) 13 { 14 Console.WriteLine("Print value:{0}", count); 15 count++; 16 await Task.Delay(500); 17 } 18 Console.WriteLine("Task Void 结束"); 19 } 20 21 async static void Entry() 22 { 23 // 创建一个无参数无返回值的方法 24 var taskVoid = Task.Factory.StartNew(CountAsync); 25 26 await taskVoid.Result; 27 // 创建一个 taskVoid 之后才能运行的方法 28 var taskContinue = taskVoid.ContinueWith(async (t) => 29 { 30 31 Console.WriteLine("这是在 TaskVoid 运行之后才能运行的 TaskContinue,Task void 的状态是 {0}", t.Status); 32 await Task.Delay(1000); 33 }); 34 ; 35 }
个人理解:
await 作为语法糖,起作用的对象是 Task ,void 方法返回的不是 Task 自然对之前的写法来讲不起作用。Task 用一个“新线程” 去执行 void 方法,自然立即就“返回”了,但返回并不表示方法结束了,方法内还是按照自己的逻辑去运行了,所以这个时候我只能眼巴巴看着 void 类型的 Count 返回却无力控制它。改成后面的写法之后,改造后的 CountAsync 方法返回的是 Task,意思是告诉上层“我是一个Task”,这个时候 await 就可以起作用了。但直接写 await taskVoid 依旧不能起作用,因为此时 taskVoid 无返回值,也就是立即返回了,所以必须要写成“var taskVoid = Task.Factory.StartNew(CountAsync)” + "await taskVoid.Result"的方式,StartNew 方法会内部新建一个 Task 并返回,这个 Task 的返回值是 CountAsync,而 Result 表示了“我要等待运行 CountAsync 这个异步任务的异步任务的结果返回来”。因此,await 起了作用,执行顺序正确了。
本人学艺不精,对 .Net 内部基本没什么涉猎,如有老鸟看到希望能提出指正意见,谢谢。