如何从C#中的同步方法调用异步方法?
我有一个 public async void Foo()
方法,我想从同步方法中调用它.到目前为止,我从 MSDN 文档中看到的所有内容都是通过异步方法调用异步方法,但我的整个程序不是用异步方法构建的.
I have a public async void Foo()
method that I want to call from synchronous method. So far all I have seen from MSDN documentation is calling async methods via async methods, but my whole program is not built with async methods.
这甚至可能吗?
以下是从异步方法调用这些方法的一个示例:
演练:使用 Async 和 Await 访问 Web(C# 和 Visual Basic)
Here's one example of calling these methods from an asynchronous method:
Walkthrough: Accessing the Web by Using Async and Await (C# and Visual Basic)
现在我正在考虑从同步方法中调用这些异步方法.
Now I'm looking into calling these async methods from sync methods.
异步编程确实成长"了通过代码库.它已经 与僵尸病毒相比.最好的解决方案是让它成长,但有时这是不可能的.
Asynchronous programming does "grow" through the code base. It has been compared to a zombie virus. The best solution is to allow it to grow, but sometimes that's not possible.
我在我的 Nito.AsyncEx 库中编写了一些类型来处理部分异步代码根据.不过,没有任何解决方案适用于所有情况.
I have written a few types in my Nito.AsyncEx library for dealing with a partially-asynchronous code base. There's no solution that works in every situation, though.
解决方案 A
如果您有一个不需要同步回其上下文的简单异步方法,那么您可以使用Task.WaitAndUnwrapException
:
If you have a simple asynchronous method that doesn't need to synchronize back to its context, then you can use Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
您不想想使用 Task.Wait
或 Task.Result
因为它们将异常包装在 AggregateException
中.
You do not want to use Task.Wait
or Task.Result
because they wrap exceptions in AggregateException
.
此解决方案仅适用于 MyAsyncMethod
不同步回其上下文的情况.换句话说,MyAsyncMethod
中的每个 await
都应该以 ConfigureAwait(false)
结尾.这意味着它无法更新任何 UI 元素或访问 ASP.NET 请求上下文.
This solution is only appropriate if MyAsyncMethod
does not synchronize back to its context. In other words, every await
in MyAsyncMethod
should end with ConfigureAwait(false)
. This means it can't update any UI elements or access the ASP.NET request context.
方案 B
如果 MyAsyncMethod
确实需要同步回其上下文,那么您可以使用 AsyncContext.RunTask
来提供嵌套上下文:
If MyAsyncMethod
does need to synchronize back to its context, then you may be able to use AsyncContext.RunTask
to provide a nested context:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
*更新 4/14/2014:在该库的更新版本中,API 如下:
*Update 4/14/2014: In more recent versions of the library the API is as follows:
var result = AsyncContext.Run(MyAsyncMethod);
(在这个例子中使用 Task.Result
是可以的,因为 RunTask
会传播 Task
异常).
(It's OK to use Task.Result
in this example because RunTask
will propagate Task
exceptions).
您可能需要 AsyncContext.RunTask
而不是 Task.WaitAndUnwrapException
的原因是因为在 WinForms/WPF/SL/ASP.NET 上发生了相当微妙的死锁可能性:
The reason you may need AsyncContext.RunTask
instead of Task.WaitAndUnwrapException
is because of a rather subtle deadlock possibility that happens on WinForms/WPF/SL/ASP.NET:
- 同步方法调用异步方法,获取
Task
. - 同步方法对
Task
进行阻塞等待. -
async
方法使用await
而没有ConfigureAwait
. -
Task
在这种情况下无法完成,因为它只有在async
方法完成后才能完成;async
方法无法完成,因为它试图将其继续安排到SynchronizationContext
,并且 WinForms/WPF/SL/ASP.NET 将不允许继续运行,因为同步方法已在该上下文中运行.
- A synchronous method calls an async method, obtaining a
Task
. - The synchronous method does a blocking wait on the
Task
. - The
async
method usesawait
withoutConfigureAwait
. - The
Task
cannot complete in this situation because it only completes when theasync
method is finished; theasync
method cannot complete because it is attempting to schedule its continuation to theSynchronizationContext
, and WinForms/WPF/SL/ASP.NET will not allow the continuation to run because the synchronous method is already running in that context.
这就是为什么在每个 async
方法中尽可能多地使用 ConfigureAwait(false)
是个好主意的原因之一.
This is one reason why it's a good idea to use ConfigureAwait(false)
within every async
method as much as possible.
方案 C
AsyncContext.RunTask
不会在所有情况下都有效.例如,如果 async
方法等待一些需要 UI 事件才能完成的事情,那么即使使用嵌套上下文,您也会死锁.在这种情况下,您可以在线程池上启动 async
方法:
AsyncContext.RunTask
won't work in every scenario. For example, if the async
method awaits something that requires a UI event to complete, then you'll deadlock even with the nested context. In that case, you could start the async
method on the thread pool:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
然而,这个解决方案需要一个 MyAsyncMethod
将在线程池上下文中工作.因此它无法更新 UI 元素或访问 ASP.NET 请求上下文.在这种情况下,您也可以在其 await
语句中添加 ConfigureAwait(false)
,并使用解决方案 A.
However, this solution requires a MyAsyncMethod
that will work in the thread pool context. So it can't update UI elements or access the ASP.NET request context. And in that case, you may as well add ConfigureAwait(false)
to its await
statements, and use solution A.
更新,2019 年 5 月 1 日:当前的最差做法"位于此处为 MSDN 文章.
Update, 2019-05-01: The current "least-worst practices" are in an MSDN article here.