如何在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上发生了一个相当微小的死锁可能性:

  • 同步方法调用异步方法,获取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#?

    下一篇: Byte Array to Bitmap Image