我有一个public async void Foo()
方法,我想从同步方法中调用它。到目前为止,我从 MSDN 文档中看到的所有内容都是通过异步方法调用异步方法,但是我的整个程序不是使用异步方法构建的。
这有可能吗?
这是从异步方法调用这些方法的一个示例: http : //msdn.microsoft.com/zh-cn/library/hh300224(v=vs.110).aspx
现在,我正在研究从同步方法调用这些异步方法。
异步编程确实在代码库中 “增长”。人们已经将其与僵尸病毒进行了比较 。最好的解决方案是让它成长,但是有时候这是不可能的。
我在Nito.AsyncEx库中编写了一些类型,用于处理部分异步的代码库。但是,没有一种解决方案可以在每种情况下都适用。
解决方案 A
如果您有一个简单的异步方法,不需要同步回其上下文,则可以使用Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
你不希望使用Task.Wait
或Task.Result
因为包装在异常AggregateException
。
仅当MyAsyncMethod
不同步回到其上下文时,此解决方案才适用。换句话说, MyAsyncMethod
每个await
都应以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 将不允许继续运行,因为同步方法已在该上下文中运行。 这就是为什么最好在每个async
方法中都使用ConfigureAwait(false)
原因之一。
解决方案 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 文章在此处提供了当前的 “最差实践”。
添加最终解决了我的问题的解决方案,希望可以节省一些时间。
首先阅读Stephen Cleary 的几篇文章:
根据 “不要阻止异步代码” 中的 “两个最佳实践”,第一个不适用于我,第二个不适用于(基本上,如果我可以使用await
,我可以!)。
所以这是我的解决方法:将调用包装在Task.Run<>(async () => await FunctionAsync());
并希望不再有僵局 。
这是我的代码:
public class LogReader
{
ILogger _logger;
public LogReader(ILogger logger)
{
_logger = logger;
}
public LogEntity GetLog()
{
Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
return task.Result;
}
public async Task<LogEntity> GetLogAsync()
{
var result = await _logger.GetAsync();
// more code here...
return result as LogEntity;
}
}
Microsoft 建立了一个 AsyncHelper(内部)类来将 Async 作为 Sync 运行。源看起来像:
internal static class AsyncHelper
{
private static readonly TaskFactory _myTaskFactory = new
TaskFactory(CancellationToken.None,
TaskCreationOptions.None,
TaskContinuationOptions.None,
TaskScheduler.Default);
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
return AsyncHelper._myTaskFactory
.StartNew<Task<TResult>>(func)
.Unwrap<TResult>()
.GetAwaiter()
.GetResult();
}
public static void RunSync(Func<Task> func)
{
AsyncHelper._myTaskFactory
.StartNew<Task>(func)
.Unwrap()
.GetAwaiter()
.GetResult();
}
}
Microsoft.AspNet.Identity 基类仅具有 Async 方法,为了将它们称为 Sync,有些类的扩展方法如下所示(示例用法):
public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}
public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}
对于那些关心代码许可条款的人,这里是指向非常相似的代码(只是增加了对线程的区域性的支持)的链接,该链接带有注释以表明它已获得 MIT 的 Microsoft 许可。 https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs