zoukankan      html  css  js  c++  java
  • 又踩.NET Core的坑:在同步方法中调用异步方法Wait时发生死锁(deadlock)

    之前在将 Memcached 客户端 EnyimMemcached 迁移 .NET Core 时被这个“坑”坑的刻骨铭心(详见以下链接),当时以为只是在构造函数中调用异步方法(注:这里的异步方法都是指基于Task的)才会出线死锁(deadlock)问题。

    最近在使用 redis 客户端 StackExchange.Redis 时也遇到了这个问题, 详见 ASP.NET Core中StackExchange.Redis连接redis服务器的问题 。

    StackExchange.Redis 中死锁问题发生在下面的代码:

    private static ConnectionMultiplexer ConnectImpl(Func<ConnectionMultiplexer> multiplexerFactory, TextWriter log)
    {
        IDisposable killMe = null;
        try
        {
            var muxer = multiplexerFactory();
            killMe = muxer;
            // note that task has timeouts internally, so it might take *just over* the regular timeout
            var task = muxer.ReconfigureAsync(true, false, log, null, "connect");
    
            if (!task.Wait(muxer.SyncConnectTimeout(true)))
            {
                task.ObserveErrors();
                if (muxer.RawConfig.AbortOnConnectFail)
                {
                    throw ExceptionFactory.UnableToConnect("Timeout");
                }
            }
            if (!task.Result) throw ExceptionFactory.UnableToConnect(muxer.failureMessage);
            killMe = null;
            return muxer;
        }
        finally
        {
            if (killMe != null) try { killMe.Dispose(); } catch { }
        }
    }

    ConnectImpl() 是一个同步方法,muxer.ReconfigureAsync() 是一个 async 异步方法。在 Linux 上运行时, task.Wait(muxer.SyncConnectTimeout(true)) 会因为等待超时而返回 false ,从而出现下面的错误:

    StackExchange.Redis.RedisConnectionException: It was not possible to connect to the redis server(s); to create a disconnected multiplexer, disable AbortOnConnectFail. Timeout
       at StackExchange.Redis.ConnectionMultiplexer.ConnectImpl(Func`1 multiplexerFactory, TextWriter log)

    如果改为 task.Wait() ,在 ASP.NET Core 程序中调用时,请求会因为死锁而卡死。

    这是一个典型的同步方法调用异步方法在 Wait 时的死锁问题(详见 Don't Block on Async Code),通常的解决方法是使用 .ConfigureAwait(false); (异步任务执行完成时不获取SynchronizationContext)。StackExchange.Redis 的开发者当然知道这一点,在 muxer.ReconfigureAsync()  中调用每一个异步方法时都加上了 .ConfigureAwait(false); 。但我们遇到的实际情况显示,这一招在 .NET Framework 中管用,在 .NET Core 中却不管用,这可能与 .NET Core 在异步机制上的改变有关,比如在异步方法中 System.Threading.SynchronizationContext.Current 的值总是为 null ,详见 ASP.NET Core 1.0 SynchronizationContext 。

    这个问题在 Liunx 上很容易出现,而在 Windows 上需要一定的并发请求才会出现。

    后来在 Microsoft.AspNetCore.DataProtection.AzureStorage 中也发现了在同步方法中调用异步方法的代码:

    public IReadOnlyCollection<XElement> GetAllElements()
    {
        var blobRef = CreateFreshBlobRef();
    
        // Shunt the work onto a ThreadPool thread so that it's independent of any
        // existing sync context or other potentially deadlock-causing items.
    
        var elements = Task.Run(() => GetAllElementsAsync(blobRef)).GetAwaiter().GetResult();
        return new ReadOnlyCollection<XElement>(elements);
    }

    参考上面的代码,在 StackExchange.Redis 中的 ConnectImpl() 方法中改为 Task.Run() 调用异步方法,死锁问题依旧。

  • 相关阅读:
    The 2019 China Collegiate Programming Contest Harbin Site A
    牛客练习赛15
    Wannafly挑战赛13-C
    Wannafly挑战赛13-D
    Subsequence Counting
    Minimize the error
    Educational Codeforces Round 42 (Rated for Div. 2)
    K-th Number
    Wannafly挑战赛13-E
    Minimum spanning tree for each edge
  • 原文地址:https://www.cnblogs.com/dudu/p/6251266.html
Copyright © 2011-2022 走看看