C#中如何从同步方法调用异步方法?
我有一个 public async Task Foo()
方法,我想从同步方法调用它。到目前为止,我从 MSDN 文档中看到的都是通过 async
方法调用 async
方法,但我的整个程序并不是使用 async
方法构建的。
这可能吗?
以下是从异步方法调用这些方法的一个示例:
演练:访问使用 Async 和 Await 进行 Web(C# 和 Visual Basic)
现在,我正在考虑从同步方法调用这些async
方法。
I have a public async Task Foo()
method that I want to call from a 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.
Is this even possible?
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 synchronous methods.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
异步编程确实通过代码库“增长”。它已经与僵尸病毒相比。最好的解决办法是让它继续生长,但有时这是不可能的。
我在 Nito.AsyncEx 库中编写了一些类型,用于处理部分异步代码库。不过,没有一种解决方案适用于所有情况。
解决方案 A
如果您有一个简单的异步方法,不需要同步回其上下文,那么您可以使用
Task.WaitAndUnwrapException
:您不< /em> 希望使用
Task.Wait
或Task.Result
因为它们将异常包装在AggregateException
中。仅当
MyAsyncMethod
未同步回其上下文时,此解决方案才适用。换句话说,MyAsyncMethod
中的每个await
都应以ConfigureAwait(false)
结尾。这意味着它无法更新任何 UI 元素或访问 ASP.NET 请求上下文。解决方案 B
如果
MyAsyncMethod
确实需要同步回其上下文,那么您可以使用AsyncContext.RunTask
来提供嵌套上下文:*更新 4/14/2014:在该库的最新版本中,API 如下:(
在此示例中使用
Task.Result
是可以的,因为RunTask
将传播任务
例外)。您可能需要
AsyncContext.RunTask
而不是Task.WaitAndUnwrapException
的原因是 WinForms/WPF/SL/ASP.NET 上发生相当微妙的死锁可能性Task
。Task
执行阻塞等待。async
方法使用await
,而不使用ConfigureAwait
。Task
无法完成,因为它仅在async
方法完成时完成;async
方法无法完成,因为它正在尝试将其延续安排到SynchronizationContext
,并且 WinForms/WPF/SL/ASP.NET 将不允许延续运行,因为同步方法已经在该上下文中运行。这就是为什么在每个
async
方法中尽可能使用ConfigureAwait(false)
是个好主意的原因之一。解决方案 C
AsyncContext.RunTask
并不适用于所有场景。例如,如果async
方法等待需要 UI 事件才能完成的操作,那么即使使用嵌套上下文,也会出现死锁。在这种情况下,您可以在线程池上启动async
方法:但是,此解决方案需要一个可在线程池上下文中工作的
MyAsyncMethod
。因此它无法更新 UI 元素或访问 ASP.NET 请求上下文。在这种情况下,您也可以将ConfigureAwait(false)
添加到其await
语句中,并使用解决方案 A。更新: 2015 MSDN 文章'异步编程 - Brownfield 异步开发',作者:Stephen Cleary。
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.
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.
Solution A
If you have a simple asynchronous method that doesn't need to synchronize back to its context, then you can use
Task.WaitAndUnwrapException
:You do not want to use
Task.Wait
orTask.Result
because they wrap exceptions inAggregateException
.This solution is only appropriate if
MyAsyncMethod
does not synchronize back to its context. In other words, everyawait
inMyAsyncMethod
should end withConfigureAwait(false)
. This means it can't update any UI elements or access the ASP.NET request context.Solution B
If
MyAsyncMethod
does need to synchronize back to its context, then you may be able to useAsyncContext.RunTask
to provide a nested context:*Update 4/14/2014: In more recent versions of the library the API is as follows:
(It's OK to use
Task.Result
in this example becauseRunTask
will propagateTask
exceptions).The reason you may need
AsyncContext.RunTask
instead ofTask.WaitAndUnwrapException
is because of a rather subtle deadlock possibility that happens on WinForms/WPF/SL/ASP.NET:Task
.Task
.async
method usesawait
withoutConfigureAwait
.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.This is one reason why it's a good idea to use
ConfigureAwait(false)
within everyasync
method as much as possible.Solution C
AsyncContext.RunTask
won't work in every scenario. For example, if theasync
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 theasync
method on the thread pool: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 addConfigureAwait(false)
to itsawait
statements, and use solution A.Update: 2015 MSDN article 'Async Programming - Brownfield Async Development' by Stephen Cleary.
添加最终解决我的问题的解决方案,希望可以节省某人的时间。
首先阅读 Stephen Cleary 的几篇文章:
来自“两个“最佳实践”中的“不要阻止异步代码”,第一个对我不起作用,第二个也不适用(基本上,如果我可以使用
await
,我就这样做!) 。所以这是我的解决方法:将调用包装在
Task.Run<>(async () =>await FunctionAsync());
中,希望不再出现死锁。这是我的代码:
Adding a solution that finally solved my problem, hopefully saves somebody's time.
Firstly read a couple articles of Stephen Cleary:
From the "two best practices" in "Don't Block on Async Code", the first one didn't work for me and the second one wasn't applicable (basically if I can use
await
, I do!).So here is my workaround: wrap the call inside a
Task.Run<>(async () => await FunctionAsync());
and hopefully no deadlock anymore.Here is my code:
Microsoft 构建了一个 AsyncHelper(内部)类来将异步作为同步运行。源代码如下所示:
Microsoft.AspNet.Identity 基类仅具有异步方法,为了将它们称为同步,有一些带有扩展方法的类,如下所示(示例用法):
对于那些关心代码许可条款的人,请参见此处是一个指向非常相似代码的链接(只是添加了对线程文化的支持),其中有注释表明它是由 Microsoft 授权的 MIT。 https://github.com/aspnet /AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
绝对不是。
简单的答案是,
首先
Task.Result 是否与 . GetAwaiter.GetResult()?
其次.Unwrap() 导致任务的设置不会阻止包装的任务。
这应该会导致任何人问
然后这将是一个It Depends。
关于 Task.Start() 、 Task.Run() 和 Task.Factory.StartNew() 的使用
摘录:
附加阅读:
指定同步上下文
ASP.NET Core SynchronizationContext
alex-from-jitbit<的观点非常好/a> 和大多数对象架构问题一样,这取决于。
作为扩展方法,您是否希望绝对每次调用都强制执行此操作,还是让使用该函数的程序员在自己的异步调用上配置该操作?我可以看到调用三个场景的用例;它很可能不是您在 WPF 中想要的东西,在大多数情况下当然有意义,但考虑到没有 ASP.Net Core 中的上下文 如果您可以保证它是 ASP.Net Core 的内部内容,那么这并不重要。
Microsoft built an AsyncHelper (internal) class to run Async as Sync. The source looks like:
The Microsoft.AspNet.Identity base classes only have Async methods and in order to call them as Sync there are classes with extension methods that look like (example usage):
For those concerned about the licensing terms of code, here is a link to very similar code (just adds support for culture on the thread) that has comments to indicate that it is MIT Licensed by Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
Absolutely not.
The easy answer is that
First off the
Is Task.Result the same as .GetAwaiter.GetResult()?
Secondly .Unwrap() causes the setup of the Task not to block the wrapped task.
Which should lead anyone to ask
Which would then be a It Depends.
Regarding usage of Task.Start() , Task.Run() and Task.Factory.StartNew()
Excerpt:
Additional Reading:
Specifying a synchronization context
ASP.NET Core SynchronizationContext
Really great point by alex-from-jitbit and as most object architectural questions go it depends.
As an extension method do you want to force that for absolutely every call, or do you let the programmer using the function configure that on their own async calls? I could see a use case for call three scenarios; it most likely is not something you want in WPF, certainly makes sense in most cases, but considering there is no Context in ASP.Net Core if you could guarantee it was say internal for a ASP.Net Core, then it wouldn't matter.
async Main()
现在是 C# 7.2 的一部分,可以在项目高级构建设置中启用。对于 C# < 7.2,正确的做法是:
你会在很多微软文档中看到这个用法,例如:
获取从 Azure 服务总线主题和订阅开始。
async Main()
is now part of C# 7.2 and can be enabled in the projects advanced build settings.For C# < 7.2, the correct way is:
You'll see this used in a lot of Microsoft documentation, for example:
Get started with Azure Service Bus topics and subscriptions.
我不是 100% 确定,但我相信 此博客应该适用于多种情况:
I'm not 100% sure, but I believe the technique described in this blog should work in many circumstances:
您将“await”关键字理解为“启动此长时间运行的任务,然后将控制权返回给调用方法”。一旦长时间运行的任务完成,它就会执行其后的代码。 wait 之后的代码与以前的 CallBack 方法类似。最大的区别是逻辑流程不会被中断,这使得写入和读取变得更加容易。
You read the 'await' keyword as "start this long running task, then return control to the calling method". Once the long-running task is done, then it executes the code after it. The code after the await is similar to what used to be CallBack methods. The big difference being the logical flow is not interrupted which makes it much easier to write and read.
然而,有一个适用于(几乎:参见评论)每种情况的良好解决方案:即席消息泵(SynchronizationContext)。
调用线程将按预期被阻塞,同时仍然确保从异步函数调用的所有延续不会死锁,因为它们将被编组到在调用线程上运行的临时 SynchronizationContext(消息泵)。
ad-hoc 消息泵助手的代码:
用法:
异步泵的更详细说明可参见 此处。
There is, however, a good solution that works in (almost: see comments) every situation: an ad-hoc message pump (SynchronizationContext).
The calling thread will be blocked as expected, while still ensuring that all continuations called from the async function don't deadlock as they'll be marshaled to the ad-hoc SynchronizationContext (message pump) running on the calling thread.
The code of the ad-hoc message pump helper:
Usage:
More detailed description of the async pump is available here.
对于任何再关注这个问题的人...
如果您查看
Microsoft.VisualStudio.Services.WebApi
,就会发现有一个名为TaskExtensions
的类。在该类中,您将看到静态扩展方法Task.SyncResult()
,它完全会阻塞线程直到任务返回。它在内部调用
task.GetAwaiter().GetResult()
,这非常简单,但是它会重载以处理任何返回Task
的async
方法。 、Task
或Task
...语法糖,宝贝...爸爸爱吃甜食。看起来
...GetAwaiter().GetResult()
是在阻塞上下文中执行异步代码的 MS 官方方法。似乎非常适合我的用例。To anyone paying attention to this question anymore...
If you look in
Microsoft.VisualStudio.Services.WebApi
there's a class calledTaskExtensions
. Within that class you'll see the static extension methodTask.SyncResult()
, which like totally just blocks the thread till the task returns.Internally it calls
task.GetAwaiter().GetResult()
which is pretty simple, however it's overloaded to work on anyasync
method that returnTask
,Task<T>
orTask<HttpResponseMessage>
... syntactic sugar, baby... daddy's got a sweet tooth.It looks like
...GetAwaiter().GetResult()
is the MS-official way to execute async code in a blocking context. Seems to work very fine for my use case.或者使用这个:
Or use this:
您可以从同步代码调用任何异步方法,也就是说,直到您需要
await
时,它们也必须标记为async
。正如很多人在这里建议的那样,您可以在同步方法中对生成的任务调用
Wait()
或 Result,但最终会在该方法中遇到阻塞调用,这有点失败异步的目的。如果您确实无法使您的方法
异步
并且您不想锁定同步方法,那么您将不得不使用回调方法,将其作为参数传递给<任务的 code>ContinueWith() 方法。You can call any asynchronous method from synchronous code, that is, until you need to
await
on them, in which case they have to be marked asasync
too.As a lot of people are suggesting here, you could call
Wait()
or Result on the resulting task in your synchronous method, but then you end up with a blocking call in that method, which sort of defeats the purpose of async.If you really can't make your method
async
and you don't want to lock up the synchronous method, then you're going to have to use a callback method by passing it as parameter to theContinueWith()
method on task.这是最简单的解决方案。我在互联网上的某个地方看到过,我不记得在哪里了,但我一直在成功地使用它。它不会使调用线程死锁。
Here is the simplest solution. I saw it somewhere on the Internet, I didn't remember where, but I have been using it successfully. It will not deadlock the calling thread.
斯蒂芬·克利里的回答;
https://blog.stephencleary.com/2012/07 /dont-block-on-async-code.html
这是方法;
Stephen Cleary's Answer;
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
And here is the approach;
受到其他一些答案的启发,我创建了以下简单的辅助方法:
可以按如下方式调用它们(取决于您是否返回值):
Inspired by some of the other answers, I created the following simple helper methods:
They can be called as follows (depending on whether you are returning a value or not):
我多年来一直使用这种方法,它还处理和传播底层异步任务的异常。效果完美。
但自从微软创建了这个异步助手: https://github.com/aspnet/AspNetIdentity/blob/main/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
这也是他们的来源:
Well I was using this approach for years, which also handles and propagates exceptions from the underlying async task. Which works flawlessly.
But since that Microsoft created this async helper: https://github.com/aspnet/AspNetIdentity/blob/main/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
Here is also their source:
现在,您可以使用源生成器通过 同步方法生成器 创建方法的同步版本库(nuget)。
按如下方式使用它:
这将生成您可以同步调用的
Foo
方法。You can now use source generators to create a sync version of your method using Sync Method Generator library (nuget).
Use it as follows:
Which will generate
Foo
method which you can call synchronously.每个人似乎都预设需要等待结果。
我经常需要从同步方法更新数据,而我并不关心结果。我只是使用丢弃:
Everyone seems to presuppose that there is a need to wait for the result.
I often have to update data from synchronous methods where I don't care about the result. I just use a discard: