阻塞与非阻塞异步代码

Posted

技术标签:

【中文标题】阻塞与非阻塞异步代码【英文标题】:Blocking vs Non Blocking Asynchronous code 【发布时间】:2019-12-27 08:37:51 【问题描述】:

我正在对一些已经编写的旧代码进行自我审查,并且想知道以下代码是否以任何方式阻塞当前线程或它一直是非阻塞的?

按照 Microsoft 的建议,我对所有应用程序使用单个 HttpClient 实例

HttpClient 旨在被实例化一次并在应用程序的整个生命周期中重复使用。为每个请求实例化一个 HttpClient 类将耗尽重负载下可用的套接字数量。这将导致 SocketException 错误。https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netframework-4.8#remarks

public virtual async Task<Tuple<string, bool>> PostAsync(string resourceUrl, string body, string basicToken)

    string methodName = "PostAsync";
    var response = new Tuple<string, bool>(string.Empty, false);
    try
    
        var content = new StringContent(body, Encoding.UTF8, "application/json");
        var request = new HttpRequestMessage(HttpMethod.Post, resourceUrl);
        request.Headers.Add("Authorization", basicToken);
        request.Content = new StringContent(body, Encoding.UTF8, "application/json");
        var httpResponse = await ApplicationWrapper.AccessTokenClient.SendAsync(request).ConfigureAwait(false);
        response = new Tuple<string, bool>(await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false), httpResponse.IsSuccessStatusCode);
    
    catch (WebException e)
    
        Util.Log(methodName + " | WebException: " + e.Message + "|" + e.StackTrace.ToString());
        using (WebResponse WebResponse = e.Response)
        
            HttpWebResponse httpResponse = (HttpWebResponse)WebResponse;
            using (var streamReader = new StreamReader(WebResponse.GetResponseStream()))
                Util.Log(methodName + " | Exception postAsync API: " + streamReader.ReadToEnd());
        
    
    catch (Exception ex)
    
        Util.Log(methodName + " | Exception: " + ex.Message + "|" + ex.StackTrace.ToString() + "|" + ex.InnerException);
    


    return response;

Microsoft 声明 ReadAsStringAsync 的事实

此操作不会阻塞。 https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpcontent.readasstringasync?view=netframework-4.8

另外,为了避免死锁,建议使用 ConfigureAwait(false)

ConfigureAwait(false) 配置任务,以便在等待后继续运行,不必在调用方上下文中运行,从而避免任何可能的死锁。 https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f

在压力下阻塞异步代码可能会影响服务器,因为线程数不断增加,CPU 利用率接近 90% ... 上面的代码中是否有任何阻塞的方面?

【问题讨论】:

catch 中的 ReadToEnd 可以替换为 ReadToEndAsync 但我不会太担心。我不确定这个问题是否适合 SO。它可能更适合 Code Review。 没有“阻塞异步代码”之类的东西。如果它是异步的,它不会阻塞。 并且没有必要使用ConfigureAwait(false),除非您正在编写一个将在其他人的项目中使用的库。它仅在有人同步等待您的异步代码时防止死锁(例如,在 Task 上使用 .Wait().Result)。如果您是唯一一个调用您的代码的人,而您从不这样做,则不需要 ConfigureAwait(false) 您使用的是什么版本的 dotnet?他们改变了关于 dotnet core 3 的建议 使用 ASP.NET mvc 5 【参考方案1】:

这一切对我来说都很好。一个简单的经验法则是,如果您在异步方法的返回值 (Task) 上看到任何出现 .Result.Wait() 的情况,那么您就阻塞了 可能 应该在的位置 @ 987654324@ing。在这里没有看到 - 您似乎正在等待所有异步调用。现在只要确保任何调用这个方法的东西也是awaiting,一直到调用堆栈。如果你在任何地方阻塞,一切都是徒劳的。 :)

【讨论】:

是的,它一直在等待堆栈 @ToddMenirr .. 当我们等待一个不会阻塞当前线程的方法时,对吧? .. 除非我们使用 Result 或等待 正确。 await 基本上指示编译器将方法的其余部分重写为回调,当 Task 完成时触发,释放当前线程。

以上是关于阻塞与非阻塞异步代码的主要内容,如果未能解决你的问题,请参考以下文章

同步与异步,阻塞与非阻塞

聊聊同步异步阻塞与非阻塞

Java 同步与异步-阻塞与非阻塞理解

理解同步异步阻塞与非阻塞

同步与异步,阻塞与非阻塞

同步与异步阻塞与非阻塞