如何在C#中使用同步方法调用异步方法?
我有一个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
情况下使用await
。 Task
无法在这种情况下完成,因为它只在async
方法完成时才完成; async
方法无法完成,因为它试图将其延续安排到SynchronizationContext
,而WinForms / WPF / SL / ASP.NET将不允许继续运行,因为同步方法已经在该上下文中运行。 这是为什么尽可能在每个async
方法中使用ConfigureAwait(false)
是一个好主意的原因之一。
解决方案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.
微软建立了一个AsyncHelper(内部)类来运行Async as 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授权的MIT。 https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
添加终于解决了我的问题的解决方案,希望能节省一些人的时间。
首先阅读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;
}
}
链接地址: http://www.djcxy.com/p/75439.html
上一篇: How to call asynchronous method from synchronous method in C#?