协慌网

登录 贡献 社区

如何在 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.WaitTask.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 上发生相当微妙的死锁:

  1. 同步方法调用异步方法,获得Task
  2. 同步方法对Task进行阻塞等待。
  3. async方法使用await而不使用ConfigureAwait
  4. 在这种情况下, 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