首页 > 代码库 > 对[yield]的浅究到发现[async][await]
对[yield]的浅究到发现[async][await]
上篇对[foreach]的浅究到发现[yield]写完后,觉得对[yield]还没有理解清楚,想起曾经看过一位大牛的帖子讲的很深刻(链接在此),回顾了下,在这里写出自己的理解,与各位分享。
一、通常的异步
现在我们假设一种平时经常遇到的情况,现有三个方法,其中funcOne和funcTwo比较耗时需要异步执行,而且他们的逻辑是必须在funcOne执行完后才可以执行funcTwo,同理funcTwo执行完后才能执行funcThree。
按照这样的设定,通常的做法请看代码段[1]:
1 public class Program 2 { 3 public delegate void CallBack(string nextName); 4 public void funcOne(CallBack callback) 5 { 6 Console.WriteLine("[One] async Continue!"); 7 Console.WriteLine("[One] do something!"); 8 callback("Called Two"); 9 } 10 public void funcTwo(CallBack callback) 11 { 12 Console.WriteLine("[Two] async Continue!"); 13 Console.WriteLine("[Two] do something!"); 14 callback("Called Three"); 15 } 16 public void funcThree(CallBack callback) 17 { 18 Console.WriteLine("[Three] do something!"); 19 callback("Called ..."); 20 } 21 static void Main() 22 { 23 Program p = new Program(); 24 //异步执行funcOne 25 ThreadPool.QueueUserWorkItem((state1)=>{ 26 p.funcOne((name1) => 27 { 28 Console.WriteLine(name1); 29 //异步执行funcTwo 30 ThreadPool.QueueUserWorkItem((state2) => 31 { 32 p.funcTwo((name2) => 33 { 34 Console.WriteLine(name2); 35 //执行funcThree 36 p.funcThree((name3) => 37 { 38 Console.WriteLine(name3); 39 //当然还有可能继续嵌套 40 Console.WriteLine("End!"); 41 }); 42 }); 43 }); 44 }); 45 }); 46 Console.Read(); 47 } 48 }
相信看完代码后我们的感觉是一样的,好繁琐,就是不断的嵌套!那有没有方法可以避免这样呢,也就是说用同步的写法来写异步程序。
二、改进后的异步
该[yield]粉墨登场了,先看代码段[2]:
1 //三个方法以及委托CallBack的定义不变,此处不再列出。 2 //新增了静态的全局变量enumerator,和静态方法funcList. 3 public static System.Collections.IEnumerator enumerator = funcList(); 4 public static System.Collections.IEnumerator funcList() 5 { 6 Program p = new Program(); 7 //异步执行funcOne 8 ThreadPool.QueueUserWorkItem((state1) => 9 { 10 p.funcOne((name1) => 11 { 12 enumerator.MoveNext(); 13 }); 14 }); 15 yield return 1; 16 Console.WriteLine("Called Two"); 17 //异步执行funcTwo 18 ThreadPool.QueueUserWorkItem((state2) => 19 { 20 p.funcTwo((name2) => 21 { 22 enumerator.MoveNext(); 23 }); 24 }); 25 yield return 2; 26 Console.WriteLine("Called Three"); 27 //执行funcThree 28 p.funcThree((name3) => 29 { 30 //当然还有可能继续嵌套 31 }); 32 Console.WriteLine("Called ..."); 33 Console.WriteLine("End!"); 34 yield return 3; 35 } 36 37 //变化后的Main函数 38 static void Main() 39 { 40 enumerator.MoveNext(); 41 Console.Read(); 42 }
现在看看,是不是清爽了一些,没有无止尽的嵌套。代码中我们只需要定义一个迭代器,在迭代器中调用需要同步执行的方法,用[yield]来分隔,各方法通过在callback里调用迭代器的MoveNext()方法来保持同步。
通过这样的实践,我们可以理解为每当[yield return ..],程序会把迭代器的[上下文环境]暂时保存下来,等到MoveNext()时,再调出来继续执行到下一个[yield return ..]。
三、是我想太多
以上纯属瞎扯,在.net 4.5之后,我们有了神器:async/await,下面看看多么简洁吧。
代码段[3]:
1 public class Program 2 { 3 public async Task funcOne() 4 { 5 Console.WriteLine("[One] async Continue!"); 6 Console.WriteLine("[One] do something!"); 7 //await ... 8 } 9 public async Task funcTwo() 10 { 11 Console.WriteLine("[Two] async Continue!"); 12 Console.WriteLine("[Two] do something!"); 13 //await ... 14 } 15 public void funcThree() 16 { 17 Console.WriteLine("[Three] do something!"); 18 } 19 public static async Task funcList() 20 { 21 Program p = new Program(); 22 await p.funcOne(); 23 await p.funcTwo(); 24 p.funcThree(); 25 //无尽的嵌套可以继续await下去。 26 Console.WriteLine("End!"); 27 } 28 static void Main() 29 { 30 funcList(); 31 Console.Read(); 32 } 33 }
我已经感觉到了您的惊叹之情。
async修饰符将方法指定为异步方法(注意!:异步不异步,并不是async说了算,如果这个方法中没有await语句,就算用了async修饰符,它依然是同步执行,因为它就没有异步基因)。
被指定为异步方法后,方法的返回值只能为Task、Task<TResult>或者void,返回的Task对象用来表示这个异步方法,你可以对这个Task对象进行控制来操作这个异步方法,详细的可以参考msdn中给出的Task解释与示例代码。
await用于挂起主线程(这里的主线程是相对的),等待这个异步方法执行完成并返回,接着才继续执行主线程的方法。用了await,主线程才能得到异步方法返回的Task对象,以便于在主线程中对它进行操作。如果一个异步方法调用时没有用await,那么它就会去异步执行,主线程不会等待,就像我们代码段[3]中Main方法里对funcList方法的调用那样。
最后就分析到这儿吧,希望各位兄弟博友能一起讨论,共同进步。
当然,别吝啬您的[赞]哦 :)