我有一个 public async void Foo()
方法,我想从同步方法调用 . 到目前为止,我从MSDN文档中看到的是通过异步方法调用异步方法,但我的整个程序不是用异步方法构建的 .
这有可能吗?
以下是从异步方法调用这些方法的一个示例:http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
现在我正在研究从同步方法调用这些异步方法 .
我有一个 public async void Foo()
方法,我想从同步方法调用 . 到目前为止,我从MSDN文档中看到的是通过异步方法调用异步方法,但我的整个程序不是用异步方法构建的 .
这有可能吗?
以下是从异步方法调用这些方法的一个示例:http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
现在我正在研究从同步方法调用这些异步方法 .
11 回答
async Main现在是C#7.2的一部分,可以在项目高级构建设置中启用 .
对于C#<7.2,正确的方法是:
最常被接受的答案并不完全正确 . 有一种适用于所有情况的解决方案:ad-hoc消息泵(SynchronizationContext) .
调用线程将按预期被阻塞,同时仍然确保从异步函数调用的所有continuation都不会死锁,因为它们将被封送到在调用线程上运行的ad-hoc SynchronizationContext(消息泵) .
ad-hoc消息泵助手的代码:
用法:
有关异步泵的详细说明,请访问here .
您将'await'关键字读作“启动此长时间运行的任务,然后将控制权返回给调用方法” . 一旦长时间运行的任务完成,它就会在它之后执行代码 . await之后的代码类似于以前的CallBack方法 . 逻辑流程的最大区别不在于中断,这使得编写和读取更加容易 .
我不是百分百肯定,但我相信this blog中描述的技术应该适用于许多情况:
添加一个最终解决了我的问题的解决方案,希望能节省一些人的时间 .
首先阅读Stephen Cleary的几篇文章:
Async and Await
Don't Block on Async Code
从"two best practices"中"Don't Block on Async Code"开始,第一个没有适用(基本上如果我可以使用
await
,我会这样做!) .所以这是我的解决方法:将调用包装在
Task.Run<>(async () => await FunctionAsync());
内,希望不再是 deadlock .这是我的代码:
Microsoft构建了一个AsyncHelper(内部)类来将Async作为Sync运行 . 来源看起来像:
Microsoft.AspNet.Identity基类只有Async方法,为了将它们称为Sync,有些类的扩展方法看起来像(示例用法):
对于那些关注代码许可条款的人来说,这里有一个链接到非常相似的代码(只是在线程上增加了对文化的支持),这些代码有注释表明它是由Microsoft授权的MIT . https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
您可以从同步代码调用任何异步方法,也就是说,直到需要对它们进行
await
,在这种情况下,它们也必须标记为async
.正如很多人在这里建议的那样,你可以在同步方法中调用结果任务上的Wait()或Result,但最后你会在该方法中调用阻塞调用,这会使异步的目的失效 .
我真的无法使你的方法
async
并且你不必通过将其作为参数传递给任务上的ContinueWith方法来使用回调方法 .异步编程通过代码库进行"grow" . 它一直是compared to a zombie virus . 最好的解决方案是让它成长,但有时这是不可能的 .
我在Nito.AsyncEx库中编写了一些类型来处理部分异步代码库 . 但是,没有适用于所有情况的解决方案 .
Solution A
如果你有一个简单的异步方法,不需要同步回其上下文,那么你可以使用
Task.WaitAndUnwrapException
:您不想使用
Task.Wait
或Task.Result
,因为它们会在AggregateException
中包装异常 .仅当
MyAsyncMethod
不同步回其上下文时,此解决方案才适用 . 换句话说,MyAsyncMethod
中的每个await
都应以ConfigureAwait(false)
结尾 . 这意味着它无法更新任何UI元素或访问ASP.NET请求上下文 .Solution B
如果
MyAsyncMethod
确实需要同步回其上下文,那么您可以使用AsyncContext.RunTask
来提供嵌套上下文:(在此示例中使用
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将不允许继续运行,因为同步方法已在该上下文中运行 .这就是为什么尽可能在每个
async
方法中使用ConfigureAwait(false)
是个好主意的一个原因 .Solution C
AsyncContext.RunTask
在每种情况下都不起作用 . 例如,如果async
方法等待需要UI事件完成的内容,那么即使使用嵌套上下文也会出现死锁 . 在这种情况下,您可以在线程池上启动async
方法:但是,此解决方案需要
MyAsyncMethod
,它将在线程池上下文中工作 . 因此它无法更新UI元素或访问ASP.NET请求上下文 . 在这种情况下,您也可以将ConfigureAwait(false)
添加到其await
语句中,并使用解决方案A.或者用这个:
那些windows异步方法有一个叫做AsTask()的漂亮的小方法 . 您可以使用此方法将该方法作为任务返回,以便您可以手动调用Wait() .
例如,在Windows Phone 8 Silverlight应用程序上,您可以执行以下操作:
希望这可以帮助!