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 task | Task.Wait or Task.Result | await |
Wait for any task to complete | Task.WaitAny | await Task.WhenAny |
Retrieve the results of multiple tasks | Task.WaitAll | await Task.WhenAll |
Wait a period of time | Thread.Sleep | await Task.Delay |
Configure Context
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。
对第三天建议概括如下,当可以使用ConfigureAwait时,尽量使用,在不能使用异步方法的程序中,不仅可以使的GUI程序有更好的性能,也是一个避免死锁好的额技能。
对建议例外的部分是除非这些方法需要上下文。
Know Your Tools
对于async 和 await有很多知识点,如下是一些常用的处理方法。
Solutions to Common Async Problems
问题 | 方案 |
Create a task to execute code | Task.Run or TaskFactory.StartNew (not the Task constructor or Task.Start) |
Create a task wrapper for an operation or event | TaskFactory.FromAsync or TaskCompletionSource<T> |
Support cancellation | CancellationTokenSource and CancellationToken |
Report progress | IProgress<T> and Progress<T> |
Handle streams of data | TPL Dataflow or Reactive Extensions |
Synchronize access to a shared resource | SemaphoreSlim |
Asynchronously initialize a resource | AsyncLazy<T> |
Async-ready producer/consumer structures | TPL Dataflow or AsyncCollection<T> |
异步资源保护,和流处理。(注:详见原文)