最近有很多网友提起await和async,呵呵,C# 5引进的语法糖。html
这个语法糖还真很差吃,能绕倒一堆初学的朋友,在网上也有不少网友关于这块知识点的争论,有对有错,今天在这里把这个误区好好讲讲。web
“await
运算符应用于异步方法中的任务,在方法的执行中插入挂起点,直到所等待的任务完成。 任务表示正在进行的工做。”sql
不要小看这两句话,内容里暗指的意思还真很多。数据库
1)await 运算符针对于异步方法编程
2)await 插入挂起点windows
3)await 等待任务完成网络
4)任务表式正在进行的工做多线程
带着上面四点,咱们暂时停下,由于提到await不由要联想到一个好基友aysncmvc
"await
仅可用于由 async 关键字修改的异步方法中"app
到这里,咱们对上面的四个关键点,提出疑问。
await 运算符针对于异步方法,那若是咱们在异步方法里添加入同步方法会怎么样?
private static async Task<TResult> XAsync() { X(); //X()同步方法 return await XXAsync(); //XXAsync()异步方法 }
而后在mainTest主线程里调用这个XAsync()方法
static void Main(string[] args) { XAsync();
OtherMethod(); }
在main方法里,网上有网友博客说道:
1)XAsync在主线程里不会被调用,直到 awiat XAsync 才会被成功调用。就像linq to sql表达式一像,首先是var results=array.select().where();语句同样,他们只是组装,
并不会执行,要等到foreach(var result in results){ ...}迭代时或者.toList()再真正的查询。
2)XAsync在主线程里会被调用,并不阻止主线程,主线程将继续执行下面的程序!缘由是咱们写这个方法时候,vs会给咱们警告,有图为据
究竟是谁正确呢?
呵呵,其实这里两种说法都不正确。
首先,XAsync()在主线程里,直接调用,确定执行,VS此时也瞎胡闹,警告咱们说,“不等待此方法”,这是有个大大的前提!那就是这个方法体内,必须是异步的!
可能说到此很差理解。
在XAsync()方法里,上面有一段同步方法X(),此时它是运行在主线程上的同步方法,会阻止主线程,在X()运行彻底后,在运行至 return await XXAsync()时,才把主线程调用权交还给调用的方法
在此过程里,并不会产生新的线程,所有运行在主线程上。
呵呵,越说越迷糊了。。。
await和async 讲白了点,他们并不会真正意义上的去产生新的线程,咱们都知道,产生线程能够用Task类或Thread类。
那么async 标注的方法究竟是什么呢?微软给咱们的一句单简的话," 仅可用于由 async 关键字修改的异步方法中"
这就说明,async是为了await起到一种“配合”做用。而是说async修饰的方法都是异步的,那也太至关然了。
在同步方法里,执行具备async修饰的方法,按同步方法来执行。也就是说X()是运行在主线程上的方法。
至于X()方法体内至于执行同步仍是异步,决定权由方法体内的方法是否据有异步功能!
await
就像咱们上面同样,XAsync()方法体内的X()就同步,那么X()执行的依然是同步方法同样,而不是运行另一线程上。
那么问题来了,那咱们都直接X()方法多好,await还有何用,或者await 为什么不直接调用下X()方法呢?。。。
因而咱们继续看下一句XXAsync()方法,咱们既然用了await 语法,为何他能够用?我能够把他去了await吗?
固然是确定的,就像这样:
private static async Task<TResult> XAsync() { X(); //X()同步方法0 XXAsync();//"异步方法1" return await XXXAsync(); //XXAsync()异步方法2 }
XXAsync() 此时是如何运行的呢?同步仍是异步?问题又返回来了,至于同步仍是异步,不是这个方法“名词”决定的,而是这个方法体内是如何执行的
如:
private static async Task XXAsync() { X(); }
此时像上面调用的方式,XXAsync()就是咱们平时的同步方法嘛!
可是改下:
private static async Task XXAsync() { Task.Run(() =>{ X(); }); }
依据用相同的方法调用XXAsync它,这个时候,真正的运行在另外“任务上”了哦,会运行在其它线程!
写到这里,咱们并无和await有关哦。
那么在XXAsync()方法前加await到底有何不一样?
这里首先要澄清的是:加不加 await 与 XXAsync()是异步仍是同步的并无关系!
await 真正的目的只有一个 在调用异步方法 XXAsync() 时挂起此方法,它认为这个方法是比较耗时的方法,主线程或调用此方法的线程不要在此方法等待。
并同时做个标记,当前执行的线程运行结束时,将由此处恢复运行,因此在await 标记的异步方法下面的代码或方法将不能运行,必须等待这个方法完成!
如:
private static async Task XAsync() { await XXAsync(); OtherMothod(); }
在运行至 await XXAsync()时,调用方法XAsync()者将再也不运行,直接返回,就像咱们程序代码中的return语句。这样作的好处是,调用线程,不将等待此耗时线程。直接让调用线程往下运行,
若是调用线程向上一直存在await 链,就像方法套方法里有return同样,会逐层向上返回,一直返回到主线程。
而每一个“子线程”去等待耗时的I/O处理,好比 操做数据库和网络流任何,这里特别强调I/O处理,试想下,咱们的程序要查询一个数据库,可能要有点时间,此时查询命令可能已运行在了sql数据库里,
若是数据库在远程另一台机器上呢?咱们的"子线程或者任务“只是等待,而此时的主线程可能已完成。
如何理解主线程已完成呢?Asp.net Mvc 的机制就在这里,咱们都知道,IIS里的线程池是有限的,每次的Client端请求,都会到线程池里取一个空闲的线程,若是主线程一直在”占用“线程池,
很快线程池就会被利用完啦。此时咱们平时说的”吞吐量“的高低就是与此息息相关!当线程池被请求完后,再次有新的Client端请求,要会等待线程池的释放。
而mvc 就引用了控制器里异步方法的机制,原理就是让耗时的线程,直接返回,交给主线程,从而主线程会第一时间释放线程池的占用,而耗时的子线程完成时,将会在await标记从继续运行,
由此能够看出Mvc的异步将大大提升了应用程序的”吞吐量“。
至于具体的mvc异步编程机制与原理,网上一大把,也能够看看mvc的源代码,这里只简单的说下,本文的主旨await标记给异步带来的做用。
话题转回来:
那么咱们什么时候在调用异步方法用await 做”标记“呢?
看看microsoft的经典例子
// Three things to note in the signature: // - The method has an async modifier. // - The return type is Task or Task<T>. (See "Return Types" section.) // Here, it is Task<int> because the return statement returns an integer. // - The method name ends in "Async." async Task<int> AccessTheWebAsync() { // You need to add a reference to System.Net.Http to declare client. HttpClient client = new HttpClient(); // GetStringAsync returns a Task<string>. That means that when you await the // task you'll get a string (urlContents). Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com"); 1) // You can do work here that doesn't rely on the string from GetStringAsync. DoIndependentWork(); 2) // The await operator suspends AccessTheWebAsync. // - AccessTheWebAsync can't continue until getStringTask is complete. // - Meanwhile, control returns to the caller of AccessTheWebAsync. // - Control resumes here when getStringTask is complete. // - The await operator then retrieves the string result from getStringTask. string urlContents = await getStringTask; 3) // The return statement specifies an integer result. // Any methods that are awaiting AccessTheWebAsync retrieve the length value. return urlContents.Length; }
对上面的例子作了1)、2)、3)红色标记,
在1)处定义了一个网络流操做,认为此方法可能会耗时,若是在此处,添加一个await client.GetStringAsync("http://msdn.microsoft.com")
对程序来讲,这是一个异步操做,开辟了一个新线程,同时程序在此处返回给被调线程或UI,等网络流返回结束时继续在运换醒被调线程或主线程,并因为继续往下运行其它方法。
对于像这样的网站,一级一级的向上await 不会形成任何的吞吐量或响应速度的下降,但是新的问题来了,接下来2)处
DoIndependentWork()方法必须等到1)完成才能继续运行,“跳出来站在更高点看下”,这不至关于”同步“了吗?按顺序一步一步来的,子线程关没有给咱们太多的优点。
是的,确实如此。
咱们知道,让“子线程或任务”干事情,主线程继续他的活儿才对的,因此在1)处,不该该用await,让”线程或任务再跑一会“。
因为咱们没有加await,因而主线程或调用线程与调用网络流的子线程”一块运行“了。
当程序运行至3)时,1)标记处的任务可能已经完成或者快要完成,此时用了await目的只有一个,下面的一句话 urlContents.Length 要用到异步结果,必须待等结束,并同时向调用线程或主线程返回标记,
以使调用者最快的响应,而不是等待以致于阻塞。
回过头来看下:咱们即要多线程或多任务执行咱们的程序,让耗时的任务获得执行,同时又要给调用者快速响应,无论他有没有完成任务! 这才是真正的目的。
再想一想咱们前面说的,DoIndependentWork()调用,加不加await,方法确定是执行的,同时与该方法异步仍是同步也没有关系,只是要不要作”标记“而已
至于加不加标记,就是上面咱们解释的理由,忘了,回过头来看看吧
再来看看下面的问题:
若是一个方法里存在多个await,会如何执行呢?
咱们能够按照上面的猜测下,await某些时候功能很像return,多个await,相必,第一个awiat会返回,并做标记,后面的任何代码都要等待 如:
private static async TaskXAsync() { await XXAsync(); await XXXAsync(); }
事实状况确实如此,XXXAsync()必须等待XXAsync()方法执行结束!此时不会影响调用者的响应速度,但会影响咱们代码的执行效率,这点和两个共步方法稍有区别
如
private static async TaskXAsync() { XX(); XXX(); }
像上面的例子XX()和XXX()两同步方法,不只按顺序执行,并且调用者也没法拿回调用权,也就是没法及时响应,必须待两个方法都结束为止。
”偷偷的想下“,我想在XX(),XXX()方法前加一个await 不就好了吗?
回过头来想一想,上面说过: 仅可用于由 async 关键字修改的异步方法中""await
实际上咱们在VS加上那么await会报错,编译不过! 但愿破灭。。。此时已经看出被async修饰的目的。由于XX()和XXX()并没被修饰。
那好了,咱们就强制在同步方法上用async !
XX()
{
code here...
}
实际上当咱们强制在XX()方法上加上Async时VS已经提示以下:
很显然,同步方法,想提升调用者的响应速度是不可能仅仅靠async 就能完成的!根本缘由就是调用者与执行方法在同一个线程上。
再回过头来继续咱们上面的例子
private static async TaskXAsync() { await XXAsync(); await XXXAsync(); }
上面已清楚,这两个仅仅按顺序执行,并不能并行执行,势必影响执行效率,那么如何才能让他们并行执行呢?
microsoft有专门的方法 Task.WhenAll(Tasks) 咱们能够看看microsoft的例子 如:
await SumPageSizesAsync();
private async Task SumPageSizesAsync() { // Make a list of web addresses. List<string> urlList = SetUpURLList(); // Declare an HttpClient object and increase the buffer size. The // default buffer size is 65,536. HttpClient client = new HttpClient() { MaxResponseContentBufferSize = 1000000 }; // Create a query. IEnumerable<Task<int>> downloadTasksQuery = from url in urlList select ProcessURL(url, client); // Use ToArray to execute the query and start the download tasks. Task<int>[] downloadTasks = downloadTasksQuery.ToArray(); // You can do other work here before awaiting. // Await the completion of all the running tasks. int[] lengths = await Task.WhenAll(downloadTasks); //// The previous line is equivalent to the following two statements. //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks); //int[] lengths = await whenAllTask; int total = lengths.Sum(); //var total = 0; //foreach (var url in urlList) //{ // // GetByteArrayAsync returns a Task<T>. At completion, the task // // produces a byte array. // byte[] urlContent = await client.GetByteArrayAsync(url); // // The previous line abbreviates the following two assignment // // statements. // Task<byte[]> getContentTask = client.GetByteArrayAsync(url); // byte[] urlContent = await getContentTask; // DisplayResults(url, urlContent); // // Update the total. // total += urlContent.Length; //} // Display the total count for all of the web addresses. resultsTextBox.Text += string.Format("\r\n\r\nTotal bytes returned: {0}\r\n", total); }
// The actions from the foreach loop are moved to this async method. async Task<int> ProcessURL(string url, HttpClient client) { byte[] byteArray = await client.GetByteArrayAsync(url); DisplayResults(url, byteArray); return byteArray.Length; }
private List<string> SetUpURLList() { List<string> urls = new List<string> { "http://msdn.microsoft.com", "http://msdn.microsoft.com/en-us/library/hh290136.aspx", "http://msdn.microsoft.com/en-us/library/ee256749.aspx", "http://msdn.microsoft.com/en-us/library/hh290138.aspx", "http://msdn.microsoft.com/en-us/library/hh290140.aspx", "http://msdn.microsoft.com/en-us/library/dd470362.aspx", "http://msdn.microsoft.com/en-us/library/aa578028.aspx", "http://msdn.microsoft.com/en-us/library/ms404677.aspx", "http://msdn.microsoft.com/en-us/library/ff730837.aspx" }; return urls; }
上面的例子很简单了,组合任务Tasks,传给 await Task.WhenAll(Tasks) 这样,多个await 就并行获得了执行。
从上面的例子,咱们看得出,每一个嵌套的方法里,都是层层向上await,这就是await链,不只要做标记在子线程完成时,在此处”唤醒“同时达到快速响应调用线程,逐层向上返回,结果只有一个,最终让最外层的调用者及时响应,而不用等待,就像MVC原理同样,提升“吞吐量”。
可是中间有一个方法,没向上await,被调用者依然是按照执行的方式决定是同步仍是异步。被调者,要是有返回值的,调用者,是没办法获取到返回值的,由于,咱们并没办法知道,此方法是否已完成,因此,可能在之后的某段代码中依然要用await 调用。
--------------------------------------------------------------------------------------------------------------------------------------------
经过最近两天的朋友回复,这篇文章确实没有让读者们仔细理解,多是由于描述的太多,你们没有抓住中心点,下面,为了清楚的让读者理解,用一个示例流程图来讲明
你们都知道,点击事件,可能涉及到I/O耗时的方法, 若是,咱们直接用同步方法调用,可能点击事件要等许久才能反应,在此之中,咱们的事件是“假死”状态
为何会“假死”这个要好好想想,一个UI主线程在忙着!其它的事件又是走UI主线程,确定让你等等!
这个时候,就有一个异步的解决思路,主线程UI遇到耗时的方法,寻找另一个线程帮忙,主线程得以继续往下运行,主线程往下运行结束了,可能那个代忙的子线程尚未结束,
主线程不能等子线程,由于若是要继续等待,此时若是要给主线程事件,那么,主线程得不到及时响应,所以,主线程要快速的通知系统,我完成了,能够继续干其它事件。
可是问题来了,在某个时候,之前的子线程忙完了,他要把忙完的工做结果交给之前的主线程,就得唤醒之前的“主线程”。
下面经过实例来分析。
线路1:调用同步方法DoWorkSync,这里DoWorkSync 在不在主线程上执行(是否是异步方法),要进入到DoWorkSync方法体内检查代码才知道,因而进入到方法体内,仅仅有同步方法
textBox2.Text = "sysnc method",因此这里,主线程要同步完成此代码段,在DoWorkSync同步代码完成后,进入到线路2
线路2:代码运行至 await DoWorkAsync(),系统开始检查DoWorkAsync方法片段,await 标记只有在运行至异步的代码段才会打标记成功,假如
DoWorkAsync里面有同步代码,系统会先把同步代码执行完,巧合的是,里面确实有一个同步的代码片断
private async Task<string> DoWorkAsync() { DoWorkSync(); return await Task.Run(() => { return "Done with work!"; }); }
线路3:此时必须把同步方法DoWorkSync方法执行完为止,这期间都是运行在主线程上的。
线路4:当运行至Task.Run(() => { return "Done with work!"; });时,系统认为,此代码片断开启了子线程,假如说 “Done with work!”是至关耗时的任务,并同时返回结果,
主线程应该把在此处做一个标记,之后这个子线程完成时,将由此继续执行之后的代码。此时是直接return了,并无下面的代码,读者能够在此后继续写入其它方法。
既然都return了,为啥还要用await呢?此时的await就比较聪明了,他执行的是挂起此行代码,明确的告诉调用主线程不用等待,并立刻返回主线程。
这儿要着重得说下,为何可以不用等待,由于这行代码Task.Run()是开启了子线程,也就是说,把任务交给了子线程,因此主线程才得以本身解脱出来,主线程要管的是子线程完成后,要提醒。固然也能够不提醒。
主要看主线程要不要子线程的结果了。如何提醒?await做了一个标记,之后就从这儿提醒。
线路4:此时await 至关于咱们经常使用的“return”,经过await链路向上返回,就是咱们看到的线程4
线路5:当返回到button_click方法体内时,检查 DoWorkAsync()片断已检查过了,里面确实有异步方法,委托到了子线程,因而awiat 要在button处做个标记,之后这里可能会有子线程返回结果给到此处,
若是不要DoworkAsync()子线程的结果,直接不用加await,那么,能够继续执行下面的同步方法DoWorkSync(),而咱们这里加了,说明,咱们对于这里的子线程结果“很重视”,必需要拿到,才能继续下面的其它方法。
此时有读者会说,在此处等结果,不就阻碍了下面的代码运行了吗?会不会阻碍主线程?首先await 会阻止了下面的同步代码运行,但不会影响主线的响应,由于awiat 对button_click说明了,这个方法是耗时的,
不用等待,因而buttom_click 才会继续向上返回await链,进入线路5,线路5,会对上层系统说,buttom_click 事件“暂时”已完成,可让系统干其它事了。
线路6:当Task.Run(() => { return "Done with work!"; });子任务完成了,它他要通知“之前的主线程”,系统会分配“之前的主线程”因而进入了线路7
线路7:很简单,直接在button_click 里的await处 得以唤醒,继续执行如下的代码,此时能够拿到子线程的结果。
线路8:继续执行同步方法
线路9:所有方法运行结束后,通知系统,全部任务完成。
但愿读者经过这个实例流图有个理性的认识await。
小结:await与async并不能决定方法是同步仍是异步,而真正执行异步的仍是靠Task、异步委托或其它方式,await的主要做用是,
挂机耗时异步方法,把控制权及时的交给调用者,并在被调用者完成任务时,可以在此唤醒,并继续执行其它方法。
本节的内容,部分例子只起到说明做用,来原于实践的验证,因为时间仓促,并无提供完整的案例。
同时本节内容主要用简单的语言来加以说明,但愿能给读者阐明原理,若是读者但愿更清楚的知道await和async,能够查看源代码
若是对于异步的更多了解请参考:
本文部分实例参考