微软建立了一个AsyncHelper(内部)类来运行异步同步。源代码如下所示:
internal static class AsyncHelpe
{
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));
}
异步编程通过代码库“增长”。它被比作僵尸病毒。最好的解决办法是让它增长,但有时这是不可能的。
我在我的Nito.AsyncEx库中编写了几个类型来处理部分异步的代码库。尽管如此,在任何情况下都没有解决方案。
答案A
如果你有一个简单的异步方法,不需要同步到上下文,那么你可以使用Task.WaitAndUnwrapException:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
你不希望使用Task.Wait或Task.Result因为包装在异常AggregateException。
此解决方案只适用于MyAsyncMethod不同步回到上下文。换句话说,每一个await在MyAsyncMethod应该结束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将不允许继续运行,因为同步方法已经在该上下文中运行。
这是为什么尽可能ConfigureAwait(false)在每种async方法中使用它是一个好主意的原因之一。
解决方案C
AsyncContext.RunTask在任何情况下都不起作用。例如,如果async方法正在等待需要UI事件完成的事情,那么即使嵌套的上下文也会死锁。在这种情况下,您可以async在线程池中启动该方法:
var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
但是,这个解决方案需要MyAsyncMethod在线程池上下文中工作。所以它不能更新UI元素或访问ASP.NET请求上下文。在这种情况下,你可以加上ConfigureAwait(false)它的await陈述,并使用解决方案A.