如何从C#中的同步方法调用异步方法?

如何从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.WaitTask.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:

  1. 同步方法调用异步方法,获取Task.
  2. 同步方法对 Task 进行阻塞等待.
  3. async 方法使用 await 而没有 ConfigureAwait.
  4. Task 在这种情况下无法完成,因为它只有在 async 方法完成后才能完成;async 方法无法完成,因为它试图将其继续安排到 SynchronizationContext,并且 WinForms/WPF/SL/ASP.NET 将不允许继续运行,因为同步方法已在该上下文中运行.
  1. A synchronous method calls an async method, obtaining a Task.
  2. The synchronous method does a blocking wait on the Task.
  3. The async method uses await without ConfigureAwait.
  4. The Task cannot complete in this situation because it only completes when the async method is finished; the async method cannot complete because it is attempting to schedule its continuation to the SynchronizationContext, 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.