异步编程确实在代码库中“增长”。人们已经将其与僵尸病毒进行了比较。最好的解决方案是允许它增长,但是有时这是不可能的。
我在Nito.AsyncEx库中编写了一些类型,用于处理部分异步的代码库。但是,没有一种解决方案可以在每种情况下都适用。
解决方案A
如果您有一个简单的异步方法,不需要同步回其上下文,则可以使用Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
你不希望使用Task.Wait
或Task.Result
因为包装在异常AggregateException
。
仅当MyAsyncMethod
不同步回其上下文时,此解决方案才适用。换句话说,每个await
in MyAsyncMethod
都应以结尾ConfigureAwait(false)
。这意味着它无法更新任何UI元素或访问ASP.NET请求上下文。
解决方案B
如果MyAsyncMethod
确实需要同步回其上下文,那么您可能可以AsyncContext.RunTask
用来提供嵌套的上下文:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* 2014年4月14日更新:在该库的最新版本中,API如下:
var result = AsyncContext.Run(MyAsyncMethod);
(Task.Result
在此示例中可以使用,因为RunTask
它将传播Task
异常)。
之所以需要AsyncContext.RunTask
使用它,Task.WaitAndUnwrapException
是因为WinForms / WPF / SL / ASP.NET上发生了相当细微的死锁:
- 同步方法调用异步方法,获得
Task
。
- 同步方法对进行阻塞等待
Task
。
- 该
async
方法await
不使用ConfigureAwait
。
- 在
Task
这种情况无法完成,因为它只有在完成async
方法完成; 该async
方法无法完成,因为它正尝试将其继续安排到SynchronizationContext
,并且WinForms / WPF / SL / ASP.NET将不允许继续运行,因为同步方法已在该上下文中运行。
这就是为什么ConfigureAwait(false)
在每种async
方法中尽可能多使用一个好主意的原因之一。
解决方案C
AsyncContext.RunTask
并非在所有情况下都有效。例如,如果该async
方法等待需要UI事件完成的操作,那么即使使用嵌套上下文,您也将死锁。在这种情况下,您可以async
在线程池上启动该方法:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
但是,此解决方案需要一个MyAsyncMethod
可以在线程池上下文中工作的。因此,它无法更新UI元素或访问ASP.NET请求上下文。在这种情况下,您也可以添加ConfigureAwait(false)
其await
语句,并使用解决方案A。
更新,2019-05-01:MSDN文章在此处提供了当前的“最差实践” 。