异步编制程序中的最佳做法,await异步编制程序活用基础

原版的书文链接

避免async void

异步方法重临类型有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方法相比有众多劣势,但在1个与众分裂场所很有用,那正是异步的轩然大波句柄。它们一贯将相当抛出到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。

【本文转自:http://www.cnblogs.com/x-xk/archive/2013/06/05/3118005.html
 作者:

近来来,涌现了过多关于 Microsoft .NET Framework 4.5中新增了对 async 和 await 协理的音讯。 本文意在作为学习异步编制程序的“第一步”;笔者要是您已阅读过关于那一只的起码一篇介绍性作品。 本文不提供任何新剧情,Stack Overflow、MSDN 论坛和
async/await FAQ 那类在线财富提供了同一的提出。 本文只重点介绍部分溺水在文书档案海洋中的最佳做法。

一向选拔async

那句话的意味是,不要不经过认真考虑就混合同步和异步代码。特别地,在异步代码上行使Task.Wait或Task.Result是3个馊主意。

上面是二个粗略的例证:三个方法阻塞了异步方法的结果。在控制台程序中会工作的很好,但是从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应用有1个SynchronizationContext,它只同意二次运转一大块代码。当await实现时,它尝试在捕获的左右文内执行异步方法的剩下部分。不过该上下文已经有三个线程了,它在(同步地)等待这么些async方法的姣好。它们每一个都在伺机另3个,造成了死锁。

小心控制台程序不会造成那种死锁。它们有个线程池SynchronizationContext而没有2次执行一大坨代码的SynchronizationContext,因而当await实现时,它在线程池线程上调度该async方法的剩余部分。该情势能够做到,它做到了归来task,并不曾死锁。

一言以蔽之,应该幸免混合async和鸿沟的代码。那样做的话会促成死锁,更复杂的错误处理和前后文线程不可预测的堵截。

好久没写博客了,时隔4个月,奉上一篇精心准备的篇章,希望大家能有所收获,对async
和 await 的理解有更深一层的通晓。

本文中的最佳做法更大程度上是“指点标准”,而不是实在规则。 个中每个辅导规范都有局地例外景况。 作者将解释每个指引标准背后的原故,以便能够精通地打听哪天适用以及曾几何时不适用。 
1
 中总括了那几个指点标准;笔者将在偏下各节中种种斟酌。

计划上下文

能够查阅本身的另一篇博客《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.

}

 

各样异步方法都有友好的上下文,由此只要一个异步方法调用另2个异步方法,那么它们的上下文是单独的。

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;
  }
}

后天就写到那里呢,还有很多很高档的用法,必要本身非凡研讨一下才能享用出去,希望大家多多扶助!


async 和 await 有您不明了的机要,微软会告知你呢?

图 1 异步编制程序指引标准总结

本人用自个儿要好的事例,去一步步注明那一个技术,看下去,你相对会有收获。(渐进描述格局,愿适应全数层次的程序员)

“名称” 说明 异常
避免 Async Void 最好使用 async Task 方法而不是 async void 方法 事件处理程序
始终使用 Async 不要混合阻塞式代码和异步代码 控制台 main 方法
配置上下文 尽可能使用 ConfigureAwait(false) 需要上下文的方法

从零开始, 控制台 Hello World:

避免 Async Void

Async
方法有三种恐怕的回来类型: Task、Task<T> 和 void,不过 async
方法的原有重回类型只有 Task 和 Task<T>。 当从同步转移为异步代码时,任何再次回到类型 T
的点子都会化为再次来到 Task<T> 的 async 方法,任何重临 void
的章程都会化为重临 Task 的 async 方法。 下边包车型客车代码段演示了3个赶回 void
的联名方法及其一致的异步方法:

 

void MyMethod()
{
  // Do synchronous work.
Thread.Sleep(1000);
}
async Task MyMethodAsync()
{
  // Do asynchronous work.
await Task.Delay(1000);
}

归来
void 的 async 方法具有一定用途: 用于支持异步事件处理程序。 事件处理程序能够回到有些实际类型,但不可能以连带语言平时干活;调用重回类型的事件处理程序万分狼狈,事件处理程序实际再次回到某个内容这一概念也尚无太马虎义。 事件处理程序本质上回来 void,由此 async 方法再次来到void,以便能够行使异步事件处理程序。 可是,async void 方法的一对语义与 async
Task 或 async Task<T>
方法的语义略有分化。

Async
void 方法具有分化的错误处理语义。 当 async Task 或 async Task<T>
方法引发那一个时,会捕获该尤其并将其内置 Task 对象上。 对于 async void
方法,没有 Task 对象,由此 async void 方法引发的别的尤其都会直接在
SynchronizationContext(在 async void
方法运维时处于活动状态)上吸引。 图 2 演示本质上不恐怕捕获从 async void
方法引发的尤其。

图 2 不可能使用 Catch 捕获来自 Async Void 方法的不胜

 

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
throw;
  }
}

能够通过对 GUI/ASP.NET 应用程序使用
AppDomain.UnhandledException
或接近的全方位捕获事件阅览到这个非常,可是利用那几个事件开展例行相当处理会造成力不从心维护。

Async
void 方法具有差别的结缘语义。 再次来到 Task 或 Task<T> 的 async
方法能够使用 await、Task.WhenAny、Task.WhenAll 等有益地组合而成。 重临 void 的
async 方法未提供一种简单方法,用于向调用代码布告它们已做到。 运营几个async void 方法不难,可是规定它们什么日期停止却不易。 Async void
方法会在起步和终止时通报
SynchronizationContext,但是对江小鱼规应用程序代码而言,自定义
SynchronizationContext
是一种复杂的消除方案。

Async
void 方法难以测试。 由于错误处理和组合方面包车型客车差别,因而调用 async void
方法的单元测试不易编写。 MSTest 异步测试扶助仅适用于再次回到 Task 或
Task<T> 的 async 方法。 能够设置 SynchronizationContext 来检查和测试全数async void 方法都已成功的时刻并采访全体尤其,然则只需使 async void
方法改为回去 Task,那会简单得多。

强烈,async void 方法与 async Task
方法相比有着多少个毛病,但是那个措施在一种特定情景下卓越实用:
异步事件处理程序。 语义方面的异样对于异步事件处理程序11分有意义。 它们会直接在
SynchronizationContext
上吸引那1个,这类似于联合事件处理程序的一言一动艺术。 同步事件处理程序经常是个人的,由此不能够组合或直接测试。 作者喜欢使用的3个方法是尽量减弱异步事件处理程序中的代码(例如,让它等待蕴含实际逻辑的
async Task 方法)。 上面包车型地铁代码演示了这一措施,该格局通过将 async void
方法用于事件处理程序而不牺牲可测试性:

 

private async void button1_Click(object sender, EventArgs e)
{
  await Button1ClickAsync();
}
public async Task Button1ClickAsync()
{
  // Do asynchronous work.
await Task.Delay(1000);
}

设若调用方不期望 async void
方法是异步的,则那么些主意可能会导致惨重影响。 当再次回到类型是 Task
时,调用方知道它在处理现在的操作;当重回类型是 void
时,调用方大概只要方法在回去时成功。 此难点可能会以重重出其不意形式出现。 在接口(或基类)上提供重回 void 的法子的 async
达成(或重写)经常是张冠李戴的。 有个别事件也要是其处理程序在回来时形成。 二个不易发现的骗局是将 async lambda 传递到应用 Action
参数的措施;在那种情状下,async lambda 再次来到 void 并继续 async void
方法的持不正常。 一般而言,仅当 async lambda 转换为回到 Task
的信托项目(例如,Func<Task>)时,才应选择 async
lambda。

计算这第②个指点规范正是,应首要采纳 async Task 而不是
async void。 Async Task
方法更有利于达成错误处理、可组合性和可测试性。 此指引标准的例外境况是异步事件处理程序,这类处理程序必须回到
void。 此例外境况包含逻辑上是事件处理程序的点子,即便它们字面上不是事件处理程序(例如
ICommand.Execute implementations)。

怎么着?开玩笑吗?拿异步做Hello World??

一直使用 Async

异步代码让自身纪念了三个故事,有个人提议世界是悬浮在满郁蒸的,可是二个老妇人应声提出狐疑,她申明世界位于四个英雄海龟的背上。 当这厮问水龟站在哪个地方时,老老婆回答:“很聪明,年轻人,上边是无穷无尽的水龟!”在将贰只代码转换为异步代码时,您会发觉,假如异步代码调用别的异步代码并且被别的异步代码所调用,则效果最好
— 一路向下(大概也足以说“向上”)。 别的人已注意到异步编制程序的不胫而走行为,并将其名为“传染”或将其与僵尸病毒进行相比。 无论是水龟依然僵尸,无可置疑的是,异步代码趋向于推动周围的代码也改成异步代码。 此行为是兼具项指标异步编制程序中所固有的,而不仅仅是新
async/await 关键字。

“始终异步”表示,在未多加商量后果的图景下,不应混合使用同步和异步代码。 具体而言,通过调用 Task.Wait 或 Task.Result
在异步代码上海展览中心开围堵常常很倒霉。 对于在异步编制程序方面“半上落下”的程序员,那是个专门常见的标题,他们仅仅转移一小部分应用程序,并应用一块
API 包装它,以便代码更改与应用程序的别的部分隔开。 不幸的是,他们会遇见与死锁有关的标题。 在 MSDN
论坛、Stack Overflow
和电子邮件中回应了不少与异步相关的题材之后,作者得以说,迄今停止,那是异步初学者在摸底基础知识之后最常提问的难点:
“为什么笔者的一些异步代码死锁?”


3
 演示一个粗略示例,在那之中三个主意产生阻塞,等待 async
方法的结果。 此代码仅在控制台应用程序中央银行事非凡,然而在从 GUI 或
ASP.NET 上下文调用时会死锁。 此行为恐怕会令人思疑,尤其是通过调节和测试程序单步执行时,那意味没完没了的守候。 在调用
Task.Wait
时,导致死锁的实际原因在调用堆栈中上移。

图 3 在异步代码上围堵时的大规模死锁问题

 

public static class DeadlockDemo
{
  private static async Task DelayAsync()
  {
    await Task.Delay(1000);
  }
  // This method causes a deadlock when called in a GUI or ASP.NET context.
public static void Test()
  {
    // Start the delay.
var delayTask = DelayAsync();
    // Wait for the delay to complete.
delayTask.Wait();
  }
}

那种死锁的根本原因是 await 处理上下文的法门。 暗中认可情状下,当等待未成功的 Task
时,会捕获当前“上下文”,在 Task 达成时利用该上下文恢复生机措施的实践。 此“上下文”是近期 SynchronizationContext(除非它是
null,那种气象下则为当下 TaskScheduler)。 GUI 和 ASP.NET
应用程序具有
SynchronizationContext,它每回仅允许贰个代码区块运维。 当 await
完结时,它会尝试在捕获的上下文中执行 async 方法的结余部分。 不过该上下文已涵盖三个线程,该线程在(同步)等待 async
方法成功。 它们互相等待对方,从而致使死锁。

请留意,控制台应用程序不会形成那种死锁。 它们持有线程池 SynchronizationContext
而不是每一趟执行三个区块的 SynchronizationContext,因而当 await
达成时,它会在线程池线程上安插 async 方法的剩余部分。 该办法能够成功,并成功其归来职分,由此不存在死锁。 当程序员编写测试控制台程序,观看到一些异步代码按预期情势工作,然后将同样代码移动到
GUI 或 ASP.NET
应用程序中会爆发死锁,此行为距离恐怕会令人思疑。

此题材的一流消除方案是同意异步代码通过主旨代码自然增添。 借使利用此化解方案,则会见到异步代码扩充到其入口点(平时是事件处理程序或控制器操作)。 控制台应用程序不可能完全接纳此化解方案,因为 Main
方法不能够是 async。 假使 Main 方法是
async,则大概会在达成之前再次回到,从而致使程序结束。 
4
 演示了教导标准的这一例外情形: 控制台应用程序的 Main
方法是代码能够在异步方法上过不去为数不多的二种情形之一。

图 4 Main 方法能够调用 Task.Wait 或 Task.Result

 

class Program
{
  static void Main()
  {
    MainAsync().Wait();
  }
  static async Task MainAsync()
  {
    try
    {
      // Asynchronous implementation.
await Task.Delay(1000);
    }
    catch (Exception ex)
    {
      // Handle exceptions.
}
  }
}

允许异步代码通过中央代码扩张是极品化解方案,不过那代表需举办过多始发工作,该应用程序才能反映出异步代码的实际上利益。 可因而二种方法逐步将大批量中坚代码转换为异步代码,不过那超乎了本文的范围。 在好几意况下,使用 Task.Wait 或 Task.Result
可能助长拓展局地更换,但是供给掌握死锁难题以及错误处理难点。 作者明日证实错误处理难题,并在本文前面演示怎样防止死锁难题。

各样Task 都会蕴藏两个可怜列表。 等待 Task
时,会再次掀起第一个格外,由此能够捕获特定相当类型(如
InvalidOperationException)。 可是,在 Task 上运用 Task.Wait 或
Task.Result 同步阻塞时,全部尤其都会用 AggregateException
包装后吸引。 请再一次参阅图 4。 MainAsync 中的 try/catch
会捕获特定万分类型,不过只要将 try/catch 置于 Main 中,则它会一直捕获
AggregateException。 当没有 AggregateException
时,错误处理要便于处理得多,因而小编将“全局”try/catch 置于 MainAsync
中。

迄今甘休,我出现说法了四个与异步代码上围堵有关的标题:
大概的死锁和更扑朔迷离的错误处理。 对于在 async
方法中选择阻塞代码,也有3个难点。 请考虑此简单示例:

 

public static class NotFullyAsynchronousDemo
{
  // This method synchronously blocks a thread.
public static async Task TestNotFullyAsync()
  {
    await Task.Yield();
    Thread.Sleep(5000);
  }
}

此情势不是全然异步的。 它会及时屏弃,再次回到未到位的天职,不过当它过来执行时,会共同阻塞线程正在运行的此外内容。 要是此措施是从 GUI 上下文调用,则它会阻塞 GUI
线程;如果是从 ASP.NET 请求上下文调用,则会卡住当前 ASP.NET
请求线程。 若是异步代码不一致台阻塞,则其工作功效最佳。 
5
 是将同步操作替换为异步替换的速查表。

图 5 执行操作的“异步格局”

执行以下操作… 替换以下方式… 使用以下方式
检索后台任务的结果 Task.Wait 或 Task.Result await
等待任何任务完成 Task.WaitAny await Task.WhenAny
检索多个任务的结果 Task.WaitAll await Task.WhenAll
等待一段时间 Thread.Sleep await Task.Delay

小结这第叁个指引原则正是,应防止混合使用异步代码和封堵代码。 混合异步代码和堵塞代码或许会招致死锁、更扑朔迷离的错误处理及左右文线程的意料之外阻塞。 此教导原则的例外景况是控制台应用程序的 Main
方法,或是(要是是高级用户)管理某些异步的宗旨代码。

上边这几个事例,输出什么?猜猜?

布局上下文

在本文后边,笔者总结表达了当等待未形成 Task
时默许意况下怎么捕获“上下文”,以及此捕获的上下文用于恢复生机 async
方法的执行。 
3
 中的示例演示在上下文上的还原执行怎么着与一同阻塞产生争持从而导致死锁。从前后文行为还大概会造成另3个难题 — 质量难题。 随着异步
GUI 应用程序在持续增高,大概会发觉 async 方法的多多小部件都在动用 GUI
线程作为其上下文。 那说不定会形成慢性,因为会由于“不可胜数的剪纸”而降低响应性。

若要缓解此难点,请尽量等待 ConfigureAwait
的结果。 下边包车型客车代码段表达了暗许上下文行为和 ConfigureAwait
的用法:

 

async Task MyMethodAsync()
{
  // Code here runs in the original context.
await Task.Delay(1000);
  // Code here runs in the original context.
await Task.Delay(1000).ConfigureAwait(
    continueOnCapturedContext: false);
  // Code here runs without the original
  // context (in this case, on the thread pool).
}

由此接纳 ConfigureAwait,能够兑现少量并行性:
某个异步代码能够与 GUI 线程并行运转,而不是无休止塞入零碎的做事。

除此之外质量之外,ConfigureAwait 还具有另3个重庆大学方面:
它可以幸免死锁。 再一次考虑图 3;假设向 DelayAsync
中的代码行添加“ConfigureAwait(false)”,则可制止死锁。 此时,当等待实现时,它会尝试在线程池上下文中执行 async
方法的剩余部分。 该格局能够形成,并做到其回到职责,由此不存在死锁。 倘若必要稳步将应用程序从二只转移为异步,则此方法会特别有用。

假定得以在方式中的某处使用
ConfigureAwait,则建议对该措施中然后的各类 await 都利用它。 前面曾涉及,即使等待未成功的 Task,则会捕获上下文;如果Task 已成功,则不会捕获上下文。 在不一样硬件和网络状态下,有个别义务的形成速度大概比预想速度更快,须求严峻处理在守候以前形成的回到职务。 
6
 突显了四个改动后的以身作则。

图 6 处理在等待此前到位的回来任务

 

async Task MyMethodAsync()
{
  // Code here runs in the original context.
await Task.FromResult(1);
  // Code here runs in the original context.
await Task.FromResult(1).ConfigureAwait(continueOnCapturedContext: false);
  // Code here runs in the original context.
var random = new Random();
  int delay = random.Next(2); // Delay is either 0 or 1
  await Task.Delay(delay).ConfigureAwait(continueOnCapturedContext: false);
  // Code here might or might not run in the original context.
// The same is true when you await any Task
  // that might complete very quickly.
}

比方措施中在 await 之后有所需求上下文的代码,则不应使用
ConfigureAwait。 对于 GUI 应用程序,包括其它操作 GUI
成分、编写数据绑定属性或在于特定于 GUI 的品种(如
Dispatcher/CoreDispatcher)的代码。 对于 ASP.NET 应用程序,那包蕴别的利用
HttpContext.Current 或构建 ASP.NET
响应的代码(包蕴控制器操作中的再次来到语句)。 图 7 演示 GUI
应用程序中的七个宽广情势:让 async
事件处理程序在艺术发轫时禁止使用其控制,执行某些await,然后在处理程序截至时再也启用其控制;因为那一点,事件处理程序不可能遗弃其上下文。

图 7 让 async 事件处理程序剥夺同仁一视复启用其控制

 

private async void button1_Click(object sender, EventArgs e)
{
  button1.Enabled = false;
  try
  {
    // Can't use ConfigureAwait here ...
await Task.Delay(1000);
  }
  finally
  {
    // Because we need the context here.
button1.Enabled = true;
  }
}

种种async 方法都抱有友好的上下文,由此借使三个 async 方法调用另3个 async
方法,则其上下文是独自的。 图 8 演示的代码对
7
 进行了少量变动。

图 8 各类 async 方法都有所本人的上下文

 

private async Task HandleClickAsync()
{
  // Can use ConfigureAwait here.
await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext: false);
}
private async void button1_Click(object sender, EventArgs e)
{
  button1.Enabled = false;
  try
  {
    // Can't use ConfigureAwait here.
await HandleClickAsync();
  }
  finally
  {
    // We are back on the original context for this method.
button1.Enabled = true;
  }
}

无上下文的代码可重用性更高。尝试在代码中隔离上下文相关代码与无上下文的代码,并尽恐怕裁减上下文相关代码。在
8
 中,提议将事件处理程序的拥有中央逻辑都放置三个可测试且无上下文的
async Task 方法中,仅在上下文相关事件处理程序中保存最少量的代码。尽管是编辑
ASP.NET
应用程序,如若存在叁个也许与桌面应用程序共享的主导库,请考虑在库代码中应用
ConfigureAwait。

小结那第多个指引原则便是,应尽量使用
Configure­Await。无上下文的代码对于 GUI
应用程序具有最佳质量,是一种可在应用部分 async
基本代码时防止死锁的不二法门。此引导标准的例外情形是索要上下文的方法。

图片 1

问询您的工具

有关
async 和 await 有诸多亟待精晓的始末,那本来会有点迷失方向。
9
 是大规模难点的缓解方案的迅猛参考。

图 9 常见异步难点的化解方案

问题 解决方案
创建任务以执行代码 Task.Run 或 TaskFactory.StartNew(不是 Task 构造函数或 Task.Start)
为操作或事件创建任务包装 TaskFactory.FromAsync 或 TaskCompletionSource<T>
支持取消 CancellationTokenSource 和 CancellationToken
报告进度 IProgress<T> 和 Progress<T>
处理数据流 TPL 数据流或被动扩展
同步对共享资源的访问 SemaphoreSlim
异步初始化资源 AsyncLazy<T>
异步就绪生产者/使用者结构 TPL 数据流或 AsyncCollection<T>

首先个难题是天职创立。鲜明,async
方法能够创立职分,那是最简便易行的选项。倘诺急需在线程池上运转代码,请使用
Task.Run。假设要为现有异步操作或事件创设职务包装,请使用
TaskCompletionSource<T>。下一个大规模难点是何等处理打消和进程报告。基类库
(BCL) 包罗特别用于解决那些题材的品种:
CancellationTokenSource/CancellationToken 和
IProgress<T>/Progress<T>。异步代码应接纳基于任务的异步情势(或称为
TAP,msdn.microsoft.com/library/hh873175),该格局详细表达了职分成立、撤废和速度报告。

并发的另多个难点是何等处理异步数据流。职分很棒,可是只好回到一个指标并且不得不成功一回。对于异步流,可以利用 TPL 数据流或被动扩张 (LX570x)。TPL
数据流会创造类似于主演的“网格”。凯雷德x
更加强大和便捷,可是也愈来愈难以学习。TPL 数据流和 Odysseyx
都装有异步就绪方法,10分适用于异步代码。

只有因为代码是异步的,并不代表就高枕无忧。共享财富仍需求面临珍惜,由于不能在锁中等待,因此那相比复杂。上边是二个异步代码示例,该代码如若实施几回,则大概会损坏共享状态,即便始终在同3个线程上运转也是那样:

int
value;

Task<int> GetNextValueAsync(int current);

async
Task UpdateValueAsync()

{

 
value = await GetNextValueAsync(value);

}

 

 

 

标题在于,方法读取值并在等候时挂起本身,当方法复苏执行时,它倘使值未变更。为了化解此难题,使用异步就绪 WaitAsync 重载扩充了
SemaphoreSlim 类。图 10 演示
SemaphoreSlim.WaitAsync。

图 10 SemaphoreSlim 允许异步同步

SemaphoreSlim mutex = new SemaphoreSlim(1);

int
value;

Task<int> GetNextValueAsync(int current);

async
Task UpdateValueAsync()

{

 
await mutex.WaitAsync().ConfigureAwait(false);

 
try

 
{

   
value = await GetNextValueAsync(value);

 
}

 
finally

 
{

   
mutex.Release();

 
}

}

 

 

 

异步代码日常用于伊始化随后会缓存并共享的财富。没有用于此用途的放手类型,然则 Stephen Toub 开发了
AsyncLazy<T>,其作为也正是 Task<T> 和 Lazy<T>
合两为一。该原始类型在其博客
(bit.ly/dEN178) 上开始展览了介绍,并且在小编的 AsyncEx
库 (nitoasyncex.codeplex.com)
中提供了翻新版本。

最后,有时必要一些异步就绪数据结构。TPL 数据流提供了
BufferBlock<T>,其作为就像异步就绪生产者/使用者队列。而 AsyncEx
提供了 AsyncCollection<T>,那是异步版本的
BlockingCollection<T>。

自作者盼望本文中的指引原则和指令能享有援救。异步真的是可怜棒的言语功用,将来便是早先运用它的好机会!

 1 static  void Main(string[] args) 
 2  {
 3 
 4      Task t = example1(); 
 5  } 
 6 
 7 static async  Task DoWork() 
 8 {
 9 
10   Console.WriteLine("Hello World!"); 
11   for (int i = 0; i < 3; i++) 
12   { 
13      Console.WriteLine("Working..{0}",i); 
14      await  Task.Delay(1000);//以前我们用Thread.Sleep(1000),这是它的替代方式。 
15   } 
16 } 
17 static async Task example1() 
18 { 
19     await DoWork(); 
20     Console.WriteLine("First async Run End"); 
21 }

图片 2

 

先不用看结果,来打探精晓重点字呢,你规定你对async 和await明白?

async 其实就是一个标记,标记那几个办法是异步方法。

当方法被标记为三个异步方法时,那么其方法中务须求利用await关键字。

重点在await,看字面意思是伺机,等待方法执行到位。

它一定复杂,所以要细小描述:

当编写翻译器看到await关键字后,其后的不二法门都会被更换来3个单身的形式中去独立运维。独立运转?

是或不是开发银行了另一个线程?

啊。有其一想法的同班,很正确。正是以此答案。

咱俩来看看执行各类。来证宾博(Beingmate)下笔者的那么些说法,加深我们对await的精晓。

首先从入口example1 进入:

——>碰见await Dowork()

——>此时主线程重返

——>进入DoWork

——>输出“Hello World!”

——>碰见await 关键字

——>独立执行线程重回

——>运营甘休 
咱俩看来三个出口语句,遵照自个儿的传道,最终会出几个?猜猜,动手验证答案,往往是最实在的,超过十分之六先后都不会骗大家。假设对Task有不熟习的,可以参考本身博客先前写Task的部分

图片 3程序员正是喜欢折腾,明Bellamy(Bellamy)句话能化解的
偏偏写这么多行。

 

我们前几日观察的正是,程序进入三个又贰个方法后,输出个Hello World
就没了,没有终止,没有跳出。因为是异步,所以大家看不到后续的程序运维了。

 


 

自个儿干什么要用控制台来演示那么些程序?

本条疑问,让本人做了下边包车型客车例子测试,3个深层次的难题,看不懂跳过并未丝毫影响。

分析一下这一个例子:

图片 4

 1 static  void Main(string[] args) 
 2 { 
 3      example2(); 
 4 }
 5 
 6 static async void example2() 
 7 { 
 8     await DoWork(); 
 9     Console.WriteLine("First async Run End"); 
10 }
11 
12 static async  Task DoWork() 
13     { 
14         Console.WriteLine("Hello World!"); 
15         for (int i = 0; i < 3; i++) 
16         { 
17             await  Task.Delay(1000); 
18             Console.WriteLine("Working..{0}",i); 
19         } 
20     } 

图片 5

 

运作丝毫难题,结果照旧依旧是“Hello World ”,就好像更简单了。

只顾,细节来了,example2
是void,Mani也是void,那个相同点,就如让大家能够如此做:

 

图片 6

 1        static async void Main(string[] args)//给main加个 async 
 2        { 
 3            await DoWork(); 
 4        } 
 5      static async  Task DoWork() 
 6        { 
 7            Console.WriteLine("Hello World!"); 
 8            for (int i = 0; i < 3; i++) 
 9            { 
10               await  Task.Delay(1000); 
11                Console.WriteLine("Working..{0}",i); 
12            } 
13        } 

图片 7

 

 

程序写出,编写翻译器没有不当,运营->

图片 8

  一个异步方法调用后将回到到它前边,它必须是全部的,并且线程依旧是活着的。

  而main正因为是控制台程序的进口,是重点的回到操作系统线程,所以编写翻译器会提示入口点不可能用async。

上面那种事件,笔者想大家不会面生吧?WPF就好像都用那种异步事件写法:

1 private async void button1_Click(object sender, EventArgs e)
2 {
3 
4   //….
5 
6 }

 

以此列Main入口,类推在ASP.NET 的
Page_Load上也决不加async,因为异步Load事件内的任何异步都会同步实施,死锁?
还有比那更可恨的事啊?winfrom
WPF的Load事件近日没有测试过,以往的轩然大波都有异步async了,胡乱用,错了您都不掌握找什么人。

好小细节提点到了,这一个牵出的难题也就消除了。

 


 

有心急的同桌大概就纳闷了,第二个例子,怎么才能收看在此以前的出口啊?

别急加上那句:

1        static  void Main(string[] args) 
2        { 
3            Task t = example1();   
4 
5            t.Wait();//add 
6        } 

 

出口窗口就足以看来显示器跳动再三再四输出了、、、

入门示例已经介绍完了,来细细品味一下底下的文化吧。

 


 

 Async使用基础总计

 

到此Async介绍了二种可能的归来类型:TaskTask<T>void

只是async方法的原本再次回到类型只有Task和Task<T>,所以尽量幸免使用async
void。

并不是说它没用,存在即有用,async
void用于帮忙异步事件处理程序,什么看头?(比如笔者例子里面那贰个无聊的输出呀..)只怕就像是上述提到的:

图片 9

1 private async void button1_Click(object sender, EventArgs e)
2 
3 {
4 
5 //….
6 
7 }

图片 10

有趣味的同学能够去找找(async void怎么支持异步事件处理程序)

 


 

 很是处理介绍

  async void
的措施具有分裂的错误处理语义,因为在Task和Task<T>方法引发那1个时,会捕获相当并将其内置Task对象上,方便大家查阅错误消息,而async
void,没有Task对象,没有对象直接促成非常都会一直在SynchronizationContext上掀起(SynchronizationContext是async

await的贯彻底层哦)既然涉及了SynchronizationContext,那么小编在那说一句:

SynchronizationContext 对任何编制程序人士来说都是便宜的。

无论是怎么着平台(ASP.NET、Windows 窗体、Windows Presentation Foundation
(WPF)、Silverlight 或别的),全体 .NET 程序都带有 SynchronizationContext
概念。(建议好学的同班去找找)

 

扯远了,回到以前谈到的 async
void 和 Task 卓殊,看看二种分外的结果,看看测试用例。

首先是 async void:

图片 11

 1        static  void Main(string[] args) 
 2        { 
 3            AsyncVoidException(); 
 4        } 
 5        static async void ThrowExceptionAsync() 
 6        { 
 7            throw new OutOfMemoryException(); 
 8        } 
 9        static void AsyncVoidException() 
10        { 
11            try 
12            { 
13                ThrowExceptionAsync(); 
14            } 
15            catch (Exception) 
16            {
17 
18                throw; 
19            } 
20        } 

图片 12

 

从不丝毫至极抛出,作者从前说了,它会一贯在SynchronizationContext抛出,然而实施异步的时候,它丝毫不管有没有分外,执行线程直接回到,分外直白被吞,所以根本无法捕获async
void 的要命。作者就不上海体育场合了,偷懒了。。

再看看async Task测试用例:

图片 13

 1        static  void Main(string[] args) 
 2        { 
 3            AsyncVoidException(); 
 4        } 
 5        static async Task ThrowExceptionAsync() 
 6        { 
 7            await Task.Delay(1000); 
 8            throw new OutOfMemoryException(); 
 9        } 
10        static void AsyncVoidException() 
11        { 
12            try 
13            { 
14               Task t = ThrowExceptionAsync(); 
15               t.Wait(); 
16            } 
17            catch (Exception) 
18            {
19 
20                throw; 
21            } 
22        } 

图片 14

 

预料之中啊:

图片 15

因此相比较,大家不难看出哪个实用哪个不实用。

对此async void
笔者还要闲扯一些缺点,让大家认识到,用那些确实要有扎实的底子。

  很强烈async void
那些措施未提供一种不难的章程,去通告向调用它的代码发出回馈音讯,文告是不是已经进行到位。

  运营async void方法简单,但你要规定它曾几何时甘休也是没错。

  async void
方法会在开行和终结时去布告SynchronizationContext。简单的讲,要测试async
void
不是件简单的事,但有心去打听,SynchronizationContext或者就不那样难了,它完全能够用来检测async
void 的特出。

     说了这么多缺点,该卓越些重点了:

    提出多使用async Task 而不是async void。

    async Task方法便于实现错误处理、可组合性和可测试性。

    但是对此异步事件处理程序非凡,这类处理程序必须重临void。

异步——大家既面生又熟习的情侣——死锁!

  对于异步编制程序不驾驭的程序员,大概常干那种事:

  混合使用同步和异步代码,他们只是转移一小部分应用程序,提议一段代码块,然后用同步API包装它,这么做有利于隔开分离,同步分为一块,异步分为另一块,这么做的结果是,他们不时会遇上和死锁有关的标题。

本人事先向来用控制台来写异步,咱们应该觉得,异步Task正是这么用的吧?没有丝毫阻噻,都以本来的按陈设运转和竣事。

  嗯,来个简单的事例,看看啊:

那是自家的WPF项目标测试例子:

图片 16

 1  int i = 0; 
 2  private void button_1_Click(object sender, RoutedEventArgs e) 
 3  { 
 4     
 5     textBox.Text += "你点击了按钮 "+i++.ToString()+"\t\n"; 
 6     Task t = DelayAsync(); 
 7     t.Wait(); 
 8  }  
 9  private static async Task DelayAsync() 
10  {
11 
12     MessageBox.Show("异步完成"); 
13     await Task.Delay(1000); 
14  } 

图片 17

 

为了有利于相比较,看看控制台对应的代码:

图片 18

 1        static  void Main(string[] args) 
 2        { 
 3            Task t = DelayAsync(); 
 4            t.Wait(); 
 5        } 
 6        private static async Task DelayAsync() 
 7        { 
 8            await Task.Delay(1000); 
 9            Console.WriteLine("Complet"); 
10        }

图片 19

 

控制台程序没有丝毫题材,笔者保障。

前些天来专注一下WPF代码,当自己button点击之后,应该出现的功用是:

图片 20

  看图片的作用不错。

  接着你关掉提醒框,你会发现
,那些窗口点什么都不算了。关闭的老大,作者分明本人说的科学。

  想关掉 就去任务管理器里面截止进度吧~~~

  那是一个很简单的死锁示例,小编想说的是大多的代码,在分裂的应用程序里面会有分裂等的功能,这就是它灵活和复杂性的地点

  那种死锁的根本原因是await处理上下文的不二法门。

  暗中认可景况下,等待未成功的Task时,会捕获当前“上下文”,在Task达成时使用该上下文回复方法的推行(这里的“上下文”指的是当前TaskScheduler义务调度器)

  值得注意的正是下面这几句代码:

图片 21

1   t.Wait();
2 
3 private static async Task DelayAsync() 
4 {
5 
6   MessageBox.Show("异步完成"); 
7   await Task.Delay(1000); 
8 }

图片 22

 请分明你耿耿于怀他的结构了,将来自身来细讲原理。

*  Task t  有一个线程块在等候着 DelayAsync *的推行到位。

  而 async Task DelayAsunc 在另贰个线程块中执行。

  也正是说,在 Message博克斯.Show(“异步实现”);  
这一个措施成功后,await 会持续获得 async 余下的有个别,它仍是可以够捕获到接下去的代码吗?

async的线程已经被t线程在等候了,t在等候
async的成就,而运转Task.Delay(一千)后,await就会尝试在捕获的前后文中执行async方法的多余部分,async被挤占了,它就在等待t。然后它们就竞相等待对方,从而造成死锁,锁上就不听使唤了~~~用个图来形容一下那个现象

图片 23

说根本了。

  为何控制带应用程序不会形成那种死锁?

  它们持无线程池SynchronizationContext(同步上下文),而不是历次执行1个线程块区的SynchronizationContext,以此当await达成时,它会在线程池上配备async方法的剩下部分。所以各位,在控制台写好的异步程序,移动到其他应用程序中就大概会产生死锁。

 


 

 
好,今后来消除那几个WPF的异步错误,小编想那应该会挑起大家志趣,化解难点是程序员最欣赏的活。

改Wait()为ConfigureAwait(false)像这样:

1 Task t = DelayAsync();
2 
3 t.ConfigureAwait(continueOnCapturedContext:false);//这个写法复杂了点,但从可读性角度来说是不错的,你这么写t.ConfigureAwait(false)当然也没问题

 

什么是ConfigureAwait?

官方解释:试图继续回夺取的固有上下文,则为 true,否则为 false。

倒霉领悟,笔者来详细分解下,以此办法是很有用的,它可以兑现少量并行性

  使得一些异步代码能够并行运行,而不是贰个个去履行,实行零碎的线程块工作,升高质量。

单向才是最主要,它可以幸免死锁。

  Wait造成的竞相等待,在用那个法子的时候,就能顺遂完结,如意料之中自然。当然还有引导意见要说的,假使在章程中的某处使用ConfigureAwait,则建议对该方法中,此后每种await都采纳它。

 

    
说到那,大概有个别同学以为,能防止死锁,这么好!现在就用ConfigureAwait就行了,不用什么await了。

并未一种引导格局是让程序员盲目采纳的,ConfigureAwait这几个办法,在需求上下文的代码中是用持续的。看不懂?没关系,接着看。

  await运维的是一种原始上下文,就比如这样:

1  static async Task example1() 
2  { 
3      await DoWork(); 
4      Console.WriteLine("First async Run End"); 
5  }

  多个async对应一个await ,它们本人是贰个完好无损,我们称它为本来上下文。

 

ConfigureAwait而它有可能就不是本来上下文,因为它的机能是准备夺回原来上下文。用的时候VS二零一三会帮我们机关标识出来:

图片 24

出这么些题材是作者在事变前加了3个async注解。

  添加异步标识后,ConfigureAwait就不能夺取原始上下文了,在那种情景下,事件处理程序是不能吐弃原来上下文。

大家要掌握的是:

  每一个async方法都有温馨的上下文,假使一个async方法去调用另四个async方法,则其上下文是相互独立的。

干什么这么说?独立是什么意思?小编拿个例子表达呢:

 

图片 25

 1  private async void button_1_Click(object sender, RoutedEventArgs e) 
 2  { 
 3     Task t = DelayAsunc(); 
 4     
 5      t.ConfigureAwait(false);//Error 
 6 
 7  }  
 8  private static async Task DelayAsunc() 
 9  { 
10     MessageBox.Show("异步完成"); 
11     await Task.Delay(1000); 
12  } 

图片 26

 
因为是单身的,所以ConfigureAwait无法夺取原始上下文,错误就像上越发图。

修改一下:

图片 27

 1  private async void button_1_Click(object sender, RoutedEventArgs e) 
 2  { 
 3    Task t = DelayAsunc(); 
 4 
 5    t.Wait(); 
 6  } 
 7  private static async Task DelayAsunc() 
 8  { 
 9     MessageBox.Show("异步完成"); 
10     await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext:false); 
11  }

图片 28

 

各个async 都有谈得来的上下文,因为独立所以它们中间的调用是有效的。

修改后的例子,将事件处理程序的装有焦点逻辑都位居三个可测试且无上下文的async
Task方法中,仅在上下文相关事件处理程序中保存最少量的代码。

由来,已经计算了3条异步编制程序引导原则,小编一块聚众一下那3条,方便查阅。

图片 29

   

  大家都忽略了贰个题材,或许大家根本都没想过,

大家对代码操作,一向都以一种异步编制程序。而笔者辈的代码都运作在2个操作系统线程!

来看些最简便的行使,帮忙我们能不慢的耳熟能详,并使用,才是本身想要达到的指标,你可以不在行,能够不会用,可是,你能够去主动接近它,适应它,精通它,直到完全活用。

异步编程是非同一般和有效的。

上面来做些基础的普及。笔者在此以前提到UI线程,什么是UI线程?

咱俩都碰着进程序假死状态,冻结,无响应。

微软提供了UI框架,使得你能够使用C#操作全数UI线程,虽说是UI框架,作者想大家都听过,它们包蕴:WinForms,WPF,Silverlight。

UI线程是唯一的1个方可决定1个一定窗口的线程,也是唯一的线程能检查和测试用户的操作,并对它们做出响应。

  这一次介绍就到那了。