Async/Await - Best Practices in Asynchronous Programming

refs:

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx


1)避免使用async void

一般异步返回Task, Task<T>

同步方法void转异步方法时 使用 Task返回,

同步方法
void MyMethod()
{
  // Do synchronous work.
  Thread.Sleep(1000);
}

正确
async Task MyMethodAsync()
{
  // Do asynchronous work.
  await Task.Delay(1000);
}

在异步方法中使用 void返回有特殊目地,在异步事件处理中使用。

因为事件处理一般为void方法,所以使得异步处理有了可能。


另外async void方法有不同的异常错误处理机制,

在async Task 或async Task<T>中异常会被放在task对象中,

而async void方法没有task对象,异常会在SynchronizationContext(??)中,

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    //此处捕获不到异常
    throw;
  }
}

这种异常可以通过 AppDomain.UnhandledException 或类似 catch-all  事件在GUI/ASP.NET applications, 只是不太好维护处理了。


Async void 和其他方法组合机制有不同,

返回Task 或 Task<T> 的异步方法可以容易的使用 await, Task.WhenAny, Task.WhenAll 来观察组合;

异步方法使用void不太好通知调用方任务完成了。不好判断任务完成。async void 将通知 SynchronizationContext 任务完成,

但通常SynchronizationContext 是复杂的处理思路或是应用程序代码。


async void 在事件处理中的示例

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

返回task的方法,调用者认为是future方法,而void的返回,人们认为是同步方法,已经得到结果了。


Async All the Way

尽量异步中调用异步,不要和同步方法混用,避免使用Task.Wait or Task.Result.,以免破坏异步方法的好处。

如下代码在GUI or ASP.NET中会死锁,console中不会:

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处理的上下文. 默认当任务未完成时,当前的‘上下文’是捕获的,并用来在任务完成后resume方法。这个‘上下文’是当前的SynchronizationContext, 如果是null的话,就是当前的TaskScheduler.。GUI and ASP.NET 程序有一个 SynchronizationContext 指运行一段代码运行一次。当 await完成, 准备用捕获的’上下文‘执行剩下的异步方法时,发现该’上下文‘已经有一个thread在了,该thread即同步waiting等待异步方法结束。他们彼此等待,构成死锁。

console程序使用线程池调用异步任务,不会出现死锁。


最好的解决方法,是一种延伸异步方法,直到入口处,通常是事件处理或控制器。Console程序不能如此,因为如果Main也是异步,则程序还没有开始就会直接返回结束了。


同步方法的异步替代

To Do This …Instead of This …Use This
Retrieve the result of a background taskTask.Wait or Task.Resultawait
Wait for any task to completeTask.WaitAnyawait Task.WhenAny
Retrieve the results of multiple tasksTask.WaitAllawait Task.WhenAll
Wait a period of timeThread.Sleepawait Task.Delay

Configure Context

如前所叙,捕获的’上下文‘可能会导致死锁,为避免如此,用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 当你在await后的代码需要’上下文‘。比如GUI app 需要在await后操作GUI元素。

比如 ASP.NET apps, 需要HttpContext.Current 或构件 ASP.NET 回复,包括在controller中返回状态等。


private async void button1_Click(object sender, EventArgs e)
{
  button1.Enabled = false;
  try
  {
    //不能使用ConfigureAwait,因为final中需要操作GUI元素 ...
    await Task.Delay(1000);
  }
  finally
  {
    // Because we need the context here.
    button1.Enabled = true;
  }
}

每个异步方法都有自己的’上下文‘,所以异步方法中调用其他异步方法,彼此有不同的’上下文‘。


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
  {
    // We are back on the original context for this method.
    button1.Enabled = true;
  }
}

’上下文‘是否敏感,可以把代码一分为二,尽量减少’上下文‘敏感的代码。建议把核心逻辑等协助上下文不敏感的方法中,减少上下文敏感的代码。即便写的是ASP.Net程序,

如果有一个核心库可能和桌面程序共享,那在代码库中就该考虑使用ConfigureAwait。

对第三天建议概括如下,当可以使用Configure­Await时,尽量使用,在不能使用异步方法的程序中,不仅可以使的GUI程序有更好的性能,也是一个避免死锁好的额技能。

对建议例外的部分是除非这些方法需要上下文。


Know Your Tools

对于async 和 await有很多知识点,如下是一些常用的处理方法。


Solutions to Common Async Problems

问题方案
Create a task to execute codeTask.Run or TaskFactory.StartNew (not the Task constructor or Task.Start)
Create a task wrapper for an operation or eventTaskFactory.FromAsync or TaskCompletionSource<T>
Support cancellationCancellationTokenSource and CancellationToken
Report progressIProgress<T> and Progress<T>
Handle streams of dataTPL Dataflow or Reactive Extensions
Synchronize access to a shared resourceSemaphoreSlim
Asynchronously initialize a resourceAsyncLazy<T>
Async-ready producer/consumer structuresTPL Dataflow or AsyncCollection<T>

异步资源保护,和流处理。(注:详见原文)












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值