msdn介绍:https://msdn.microsoft.com/zh-cn/library/hh191443.aspxhtml
其实很简单,标记了async的方法为异步方法,从方法的左大括号开始同步执行,直到第一个await出现就开始异步执行,主线程等待,等带await这行代码异步完了再回到主线程,而后继续往下执行。
若是后面又遇到带await语句的,又异步执行,执行完了就回来,继续同步往下。依此类推。
这样作其实就把咱们之前编写等待句柄接收信号的代码给省掉了,就一个await就搞定。web
Visual Basic 中的 Async 和 Await 关键字,以及 C# 中的 async 和 await 关键字都是异步编程的核心。 经过使用这两个关键字,你可使用 .NET framework 或 Windows 运行时中的资源轻松建立异步方法(几乎与建立同步方法同样轻松)。 经过使用被称为异步方法的 async 和 await 定义的异步方法。express
如下特征总结了使上一个示例成为异步方法的缘由。编程
方法签名包含一个 Async 或 async 修饰符。promise
按照约定,异步方法的名称以“Async”后缀为结尾。 app
返回类型为下列类型之一: dom
若是你的方法有操做数为 TResult 类型的返回语句,则为 Task<TResult>。异步
若是你的方法没有返回语句或具备没有操做数的返回语句,则为 Task。async
若是你编写的是异步事件处理程序,则为 Void(Visual Basic 中为 Sub)。ide
有关详细信息,请参见本主题后面的“返回类型和参数”。
方法一般包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操做完成方法才能继续。 同时,将方法挂起,而且控件返回到方法的调用方。 本主题的下一节将解释悬挂点发生的状况。
在异步方法中,可以使用提供的关键字和类型来指示须要完成的操做,且编译器会完成其他操做,其中包括持续跟踪控件以挂起方法返回等待点时发生的状况。 一些常规流程(例如,循环和异常处理)在传统异步代码中处理起来可能很困难。 在异步方法中,元素的编写频率与同步解决方案相同且此问题获得解决。
关系图中的数值对应于如下步骤。
事件处理程序调用并等待 AccessTheWebAsync 异步方法。
AccessTheWebAsync 可建立 HttpClient 实例并调用 GetStringAsync 异步方法如下载网站内容做为字符串。
GetStringAsync 中发生了某种状况,该状况挂起了它的进程。 可能必须等待网站下载或一些其余阻止活动。 为避免阻止资源,GetStringAsync 会将控制权出让给其调用方 AccessTheWebAsync。
GetStringAsync 返回 Task<TResult>,其中 TResult 为字符串,而且 AccessTheWebAsync 将任务分配给 getStringTask 变量。 该任务表示调用 GetStringAsync 的正在进行的进程,其中承诺当工做完成时产生实际字符串值。
因为还没有等待 getStringTask,所以,AccessTheWebAsync 能够继续执行不依赖于 GetStringAsync 得出的最终结果的其余工做。 该任务由对同步方法 DoIndependentWork 的调用表示。
DoIndependentWork 是完成其工做并返回其调用方的同步方法。
AccessTheWebAsync 已用完工做,能够不受 getStringTask 的结果影响。 接下来,AccessTheWebAsync 须要计算并返回该下载字符串的长度,但该方法仅在具备字符串时才能计算该值。
所以,AccessTheWebAsync 使用一个 await 运算符来挂起其进度,并把控制权交给调用 AccessTheWebAsync 的方法。 AccessTheWebAsync 将 Task(Of Integer) 或 Task<int> 返回至调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺。
![]() |
---|
若是 GetStringAsync(所以 getStringTask)在 AccessTheWebAsync 等待前完成,则控件会保留在 AccessTheWebAsync 中。 若是异步调用过程 (AccessTheWebAsync) 已完成,而且 AccessTheWebSync 没必要等待最终结果,则挂起而后返回到 getStringTask 将形成成本浪费。 |
在调用方内部(此示例中的事件处理程序),处理模式将继续。 在等待结果前,调用方能够开展不依赖于 AccessTheWebAsync 结果的其余工做,不然就需等待片刻。 事件处理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync。
GetStringAsync 完成并生成一个字符串结果。 字符串结果不是经过按你预期的方式调用 GetStringAsync 所返回的。 (记住,该方法已返回步骤 3 中的一个任务)。 相反,字符串结果存储在表示完成 getStringTask 方法的任务中。 await 运算符从 getStringTask 中检索结果。 赋值语句将检索到的结果赋给 urlContents。
当 AccessTheWebAsync 具备字符串结果时,该方法能够计算字符串长度。 而后,AccessTheWebAsync 工做也将完成,而且等待事件处理程序可继续使用。 在此主题结尾处的完整示例中,可确认事件处理程序检索并打印长度结果的值。
若是你不熟悉异步编程,请花 1 分钟时间考虑同步行为和异步行为之间的差别。 当其工做完成时(第 5 步)会返回一个同步方法,但当其工做挂起时(第 3 步和第 6 步),异步方法会返回一个任务值。 在异步方法最终完成其工做时,任务会标记为已完成,而结果(若是有)将存储在任务中。
示例:
public class MyClass { public MyClass() { DisplayValue(); //这里不会阻塞 System.Diagnostics.Debug.WriteLine("MyClass() End."); } public Task<double> GetValueAsync(double num1, double num2) { return Task.Run(() => { for (int i = 0; i < 1000000; i++) { num1 = num1 / num2; } return num1; }); } public async void DisplayValue() { double result = await GetValueAsync(1234.5, 1.01);//此处会开新线程处理GetValueAsync任务,而后方法立刻返回 //这以后的全部代码都会被封装成委托,在GetValueAsync任务完成时调用 System.Diagnostics.Debug.WriteLine("Value is : " + result); } }
现有已写好的帮助类。使用很简单,将方法名做为参数传进去就好了,最经常使用的是把很耗时的序列化函数传进去,以避免阻塞UI进程,形成卡顿现象,影响用户体验。
public static class TaskAsyncHelper { /// <summary> /// 将一个方法function异步运行,在执行完毕时执行回调callback /// </summary> /// <param name="function">异步方法,该方法没有参数,返回类型必须是void</param> /// <param name="callback">异步方法执行完毕时执行的回调方法,该方法没有参数,返回类型必须是void</param> public static async void RunAsync(Action function, Action callback) { Func<System.Threading.Tasks.Task> taskFunc = () => { return System.Threading.Tasks.Task.Run(() => { function(); }); }; await taskFunc(); if (callback != null) callback(); } /// <summary> /// 将一个方法function异步运行,在执行完毕时执行回调callback /// </summary> /// <typeparam name="TResult">异步方法的返回类型</typeparam> /// <param name="function">异步方法,该方法没有参数,返回类型必须是TResult</param> /// <param name="callback">异步方法执行完毕时执行的回调方法,该方法参数为TResult,返回类型必须是void</param> public static async void RunAsync<TResult>(Func<TResult> function, Action<TResult> callback) { Func<System.Threading.Tasks.Task<TResult>> taskFunc = ()=> { return System.Threading.Tasks.Task.Run(()=> { return function(); }); }; TResult rlt = await taskFunc(); if(callback != null) callback(rlt); } }
异步方法返回类型有3种,void,Task和Task<T>,void尽可能不要使用。
原理剖析:
使用async void标记的方法有不一样的错误处理语义。async Task或async Task<T>方法抛出异常时,异常会被捕获并放到Task对象上。然而,标记为async void的方法没有Task对象,因此async void方法抛出的任何异常都会直接放到SynchronizationContext(异步上下文)上,它是在async void方法开始的时候激活的。下面是一个例子:
//async void 方法不能被捕获的异常 private async void ThrowExceptionAsync() { throw new InvalidOperationException(); } public void AsyncVoidExceptions_CannotBeCaughtByCatch() { try { ThrowExceptionAsync(); } catch (Exception ) { //异常不会被捕获 throw; } }
async void有不一样的组成语法。返回Task或Task<T>的async方法可使用await Task.WhenAny或Task.WhenAll等轻易组合。而返回void的async方法没有提供简单的方式来通知它们已经完成的调用代码。启用若干个async void方法很容易,但不容易决定它们何时完成。async void方法开始和完成时会通知它们的SynchronizationContext,可是自定义的SynchronizationContext对于常规应用代码是一个复杂的解决方案。
async void方法测试很困难。因为错误处理和组合的差别,编写调用async void方法的单元测试很困难。
很明显,async void方法与async Task方法相比有不少劣势,但在一个特殊场合颇有用,那就是异步的事件句柄。它们直接将异常抛出到SynchronizationContext,这与同步的事件句柄表现很类似。同步的事件句柄一般是私有的,所以它们不能被组合或者直接测试。我想采起的方法是在异步事件句柄中最小化代码,好比,让它await一个包含实际逻辑的async Task方法,代码以下:
private async void button1_Click(object sender, EventArgs e) { await Button1ClickAsync(); } public async Task Button1ClickAsync() { //处理异步工做 await Task.Delay(1000); }
总之,对于async Task和async void,你应该更喜欢前者。async Task方法更容易错误处理,组合和测试。对于异步的事件句柄异常,必须返回void。
这句话的意思是,不要不通过认真考虑就混合同步和异步代码。特别地,在异步代码上使用Task.Wait或Task.Result是一个馊主意。
下面是一个简单的例子:一个方法阻塞了异步方法的结果。在控制台程序中会工做的很好,可是从GUI或者ASP.Net上下文中调用的时候就会死锁。死锁的实际缘由是当调用Task.Wait的时候进一步开启了调用栈。
//阻塞异步代码时的一个常见死锁问题 public static class DeadlockDemo { private static async Task DelayAsync() { await Task.Delay(1000); } // 调用 GUI 或 ASP.NET 上下文的时候会形成死锁 public static void Test() { // 开始延迟. var delayTask = DelayAsync(); // 等待延迟 delayTask.Wait(); } }
形成这种死锁的根本缘由是等待处理上下文的方式。默认状况下,当一个未完成的Task处于被等待状态时,当前上下文会被捕获而且当此任务完成时恢复该方法。这个上下文若是不为null就是当前的SynchronizationContext,在这种状况下,它是当前的TaskScheduler(任务调度者)。GUI 和ASP.NET应用有一个SynchronizationContext,它只容许一次运行一大块代码。当await完成时,它尝试在捕获的上下文内执行异步方法的剩余部分。可是该上下文已经有一个线程了,它在(同步地)等待这个async方法的完成。它们每个都在等待另外一个,形成了死锁。
注意控制台程序不会形成这种死锁。它们有个线程池SynchronizationContext而没有一次执行一大坨代码的SynchronizationContext,所以当await完成时,它在线程池线程上调度该async方法的剩余部分。该方法能够完成,它完成了返回task,并无死锁。
总之,应该避免混合async和阻塞的代码。这样作的话会形成死锁,更复杂的错误处理和上下文线程不可预测的阻塞。
能够查看个人另外一篇博客《Async and Await 异步和等待》的“避免上下文”部分。
这里稍加补充以下:
除了性能方面以外,ConfigureAwait还有另外一个重要的方面:它能够避免死锁。在“一直使用async”的代码示例中,再次思考一下:若是你在DelayAsync代码行添加“ConfigureAwait(false)”,那么死锁就会避免。此次,当await完成时,它尝试在线程池上下文内执行async方法的剩余部分。该方法能够完成,完成后返回task,而且没有死锁。这项技术对于逐渐将应用从同步转为异步特别有用。
建议将ConfigureAwait用在方法中的每一个await以后。只有当未完成的Task被等待时,才会唤起上下文被捕获;若是Task已经完成了,那么上下文不会被捕获。
async Task MyMethodAsync() { //这里的代码运行在原始 context. await Task.FromResult(1); //这里的代码运行在原始 context. await Task.FromResult(1).ConfigureAwait(continueOnCapturedContext: false); // 这里的代码运行在原始 context. var random = new Random(); int delay = random.Next(2); // delay是 0 or 1 await Task.Delay(delay).ConfigureAwait(continueOnCapturedContext: false); // 这里的代码不肯定是否运行在原始 context. }
每一个异步方法都有本身的上下文,所以若是一个异步方法调用另外一个异步方法,那么它们的上下文是独立的。
private async Task HandleClickAsync() { // 这里可使用ConfigureAwait await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext: false); } private async void button1_Click(object sender, EventArgs e) { button1.Enabled = false; try { // 这里不能使用 ConfigureAwait await HandleClickAsync(); } finally { // 返回到这个方法的原始上下文 button1.Enabled = true; } }
今天就写到这里吧,还有不少很高级的用法,须要本身好好研究一下才能分享出来,但愿你们多多支持!