首页 > 其他分享 >Don't Block on Async Code 不要阻止异步代码

Don't Block on Async Code 不要阻止异步代码

时间:2024-09-26 17:23:53浏览次数:1  
标签:Code Don GetJsonAsync ASP UI context Async NET 上下文

翻译自 Don't Block on Async Code (stephencleary.com)

This is a problem that is brought up repeatedly on the forums and Stack Overflow. I think it’s the most-asked question by async newcomers once they’ve learned the basics.
这是论坛和 Stack Overflow 上反复提出的问题。我认为这是异步新手在学习了基础知识后最常问的问题。

UI Example UI 示例

Consider the example below. A button click will initiate a REST call and display the results in a text box (this sample is for Windows Forms, but the same principles apply to any UI application).
请考虑以下示例。单击按钮将启动 REST 调用并在文本框中显示结果(此示例适用于 Windows 窗体,但相同的原则适用于任何 UI 应用程序)

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
  // (real-world code shouldn't use HttpClient in a using block; this is just example code)
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri);
    return JObject.Parse(jsonString);
  }
}

// My "top-level" method.
public void Button1_Click(...)
{
  var jsonTask = GetJsonAsync(...);
  textBox1.Text = jsonTask.Result;
}

  

The “GetJson” helper method takes care of making the actual REST call and parsing it as JSON. The button click handler waits for the helper method to complete and then displays its results.
“GetJson” 帮助程序方法负责进行实际的 REST 调用并将其解析为 JSON。按钮单击处理程序等待帮助程序方法完成,然后显示其结果。

This code will deadlock. 此代码将死锁

ASP.NET Example ASP.NET 示例

This example is very similar; we have a library method that performs a REST call, only this time it’s used in an ASP.NET context (Web API in this case, but the same principles apply to any ASP.NET application):
这个例子非常相似;我们有一个执行 REST 调用的库方法,只是这次它在 ASP.NET 上下文中使用(在本例中为 Web API,但相同的原则适用于任何 ASP.NET 应用程序)

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
  // (real-world code shouldn't use HttpClient in a using block; this is just example code)
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri);
    return JObject.Parse(jsonString);
  }
}

// My "top-level" method.
public class MyController : ApiController
{
  public string Get()
  {
    var jsonTask = GetJsonAsync(...);
    return jsonTask.Result.ToString();
  }
}

 

What Causes the Deadlock 导致死锁的原因

Here’s the situation: remember from my intro post that after you await a Task, when the method continues it will continue in a context.
情况是这样的:请记住,在我 await 一个 Task 之后,当方法继续时,它将在上下文中继续。

In the first case, this context is a UI context (which applies to any UI except Console applications). In the second case, this context is an ASP.NET request context.
在第一种情况下,此上下文是 UI 上下文(适用于除 Console 应用程序之外的任何 UI)。在第二种情况下,此上下文是 ASP.NET 请求上下文。

One other important point: an ASP.NET request context is not tied to a specific thread (like the UI context is), but it does only allow one thread in at a time. This interesting aspect is not officially documented anywhere AFAIK, but it is mentioned in my MSDN article about SynchronizationContext.
另一个重要的点是:ASP.NET 请求上下文不绑定到特定线程(就像 UI 上下文一样),但它一次只允许一个线程进入。AFAIK 在任何地方都没有正式记录这个有趣的方面,但在我关于 SynchronizationContext 的 MSDN 文章中提到了它。

So this is what happens, starting with the top-level method (Button1_Click for UI / MyController.Get for ASP.NET):
所以这就是发生的事情,从顶级方法(Button1_Click 用于 UI / MyController.Get 用于 ASP.NET):

  1. The top-level method calls GetJsonAsync (within the UI/ASP.NET context).
    顶级方法调用 GetJsonAsync (在 UI/ASP.NET 上下文中) 。
  2. GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context).
    GetJsonAsync 通过调用 HttpClient.GetStringAsync(仍在上下文中)来启动 REST 请求。
  3. GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.
    GetStringAsync 返回一个未完成的 Task,指示 REST 请求未完成。
  4. GetJsonAsync awaits the Task returned by GetStringAsync. The context is captured and will be used to continue running the GetJsonAsync method later. GetJsonAsync returns an uncompleted Task, indicating that the GetJsonAsync method is not complete.
    GetJsonAsync 等待 GetStringAsync 返回的 Task。上下文已捕获,并将用于稍后继续运行 GetJsonAsync 方法。GetJsonAsync 返回一个未完成的 Task,表示 GetJsonAsync 方法未完成。
  5. The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.
    顶级方法同步阻止 GetJsonAsync 返回的 Task。这会阻止上下文线程。
  6. … Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.
    …最终,REST 请求将完成。这将完成 GetStringAsync 返回的 Task。
  7. The continuation for GetJsonAsync is now ready to run, and it waits for the context to be available so it can execute in the context.
    GetJsonAsync 的延续现在已准备好运行,它正在等待上下文可用,以便它可以在上下文中执行。
  8. Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsync to complete, and GetJsonAsync is waiting for the context to be free so it can complete.
    僵局。顶级方法是阻止上下文线程,等待 GetJsonAsync 完成,而 GetJsonAsync 正在等待上下文空闲,以便它可以完成。

For the UI example, the “context” is the UI context; for the ASP.NET example, the “context” is the ASP.NET request context. This type of deadlock can be caused for either “context”.
对于 UI 示例,“context” 是 UI 上下文;对于 ASP.NET 示例,“context” 是 ASP.NET 请求上下文。这种类型的死锁可能是由 “context” 引起的。

 

Preventing the Deadlock 防止死锁

There are two best practices (both covered in my intro post) that avoid this situation:
有两种最佳实践(在我的介绍文章中都有介绍)可以避免这种情况:

  1. In your “library” async methods, use ConfigureAwait(false) wherever possible.
    在你的 “library” 异步方法中,尽可能使用 ConfigureAwait(false)。
  2. Don’t block on Tasks; use async all the way down.
    不要阻止 Tasks;一直使用 async。

Consider the first best practice. The new “library” method looks like this:
考虑第一个最佳实践。新的 “library” 方法如下所示:

public static async Task<JObject> GetJsonAsync(Uri uri)
{
  // (real-world code shouldn't use HttpClient in a using block; this is just example code)
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri).ConfigureAwait(false);
    return JObject.Parse(jsonString);
  }
}

This changes the continuation behavior of GetJsonAsync so that it does not resume on the context. Instead, GetJsonAsync will resume on a thread pool thread. This enables GetJsonAsync to complete the Task it returned without having to re-enter the context. The top-level methods, meanwhile, do require the context, so they cannot use ConfigureAwait(false).
这会更改 GetJsonAsync 的延续行为,使其不会在上下文中恢复。相反,GetJsonAsync 将在线程池线程上恢复。这使 GetJsonAsync 能够完成它返回的 Task,而无需重新进入上下文。同时,顶级方法确实需要上下文,因此它们不能使用 ConfigureAwait(false)。

 

Using ConfigureAwait(false) to avoid deadlocks is a dangerous practice. You would have to use ConfigureAwait(false) for every await in the transitive closure of all methods called by the blocking code, including all third- and second-party code. Using ConfigureAwait(false) to avoid deadlock is at best just a hack).
使用 ConfigureAwait(false) 来避免死锁是一种危险的做法。对于阻止代码调用的所有方法(包括所有第三方和第二方代码),必须对传递闭包中的每个await 使用 ConfigureAwait(false)。使用 ConfigureAwait(false) 来避免死锁充其量只是一个 hack)。

As the title of this post points out, the better solution is “Don’t block on async code”.
正如本文的标题所指出的,更好的解决方案是 “Don't block on async code”。

Consider the second best practice. The new “top-level” methods look like this:
考虑第二个最佳实践。新的“顶级”方法如下所示:

public async void Button1_Click(...)
{
  var json = await GetJsonAsync(...);
  textBox1.Text = json;
}

public class MyController : ApiController
{
  public async Task<string> Get()
  {
    var json = await GetJsonAsync(...);
    return json.ToString();
  }
}

This changes the blocking behavior of the top-level methods so that the context is never actually blocked; all “waits” are “asynchronous waits”.
这会更改顶级方法的阻塞行为,以便上下文永远不会真正被阻塞;所有 “waits” 都是 “asynchronous waits”。

Note: It is best to apply both best practices. Either one will prevent the deadlock, but both must be applied to achieve maximum performance and responsiveness.
注意:最好同时应用这两种最佳实践。其中任何一个都可以防止死锁,但必须同时应用这两个选项才能实现最佳性能和响应能力。

Resources 资源

This kind of deadlock is always the result of mixing synchronous with asynchronous code. Usually this is because people are just trying out async with one small piece of code and use synchronous code everywhere else. Unfortunately, partially-asynchronous code is much more complex and tricky than just making everything asynchronous.
这种死锁始终是 synchronous 代码与 asynchronous 代码混合的结果。通常这是因为人们只是尝试使用一小段代码进行异步,并在其他任何地方使用同步代码。不幸的是,部分异步代码比仅仅使所有内容异步要复杂和棘手得多。

If you do need to maintain a partially-asynchronous code base, then be sure to check out two more of Stephen Toub’s blog posts: Asynchronous Wrappers for Synchronous Methods and Synchronous Wrappers for Asynchronous Methods, as well as my AsyncEx library.
如果您确实需要维护部分异步代码库,请务必查看 Stephen Toub 的另外两篇博客文章:Asynchronous Wrappers for Synchronous Methods 和 Synchronous Wrappers for Asynchronous Methods,以及我的 AsyncEx 库

Answered Questions 已回答的问题

There are scores of answered questions out there that are all caused by the same deadlock problem. It has shown up on WinRT, WPF, Windows Forms, Windows Phone, MonoDroid, Monogame, and ASP.NET.
有大量已回答的问题都是由相同的死锁问题引起的。它已出现在 WinRT、WPF、Windows Forms、Windows Phone、MonoDroid、Monogame 和 ASP.NET 上。

标签:Code,Don,GetJsonAsync,ASP,UI,context,Async,NET,上下文
From: https://www.cnblogs.com/JosenEarth/p/18433815

相关文章

  • 【leetcode】2. 两数相加
      总体思路:1.将两个链表里的数字相加:总左往右加,存入第三方链表L3里;2.设置一个进位符t,用来存储每位相加的进位信息;3.对多出来单独的链表进行处理(只需考虑进位),接入到L3的后面。/***Definitionforsingly-linkedlist.*structListNode{*intval;*s......
  • Buildings(AtCoder Beginner Contest 372)
    #include<bits/stdc++.h>#defineendl'\n'usingll=longlong;typedefunsignedlonglongull;usingnamespacestd;voidGordenGhost();signedmain(){#ifdefGordenfreopen("E:/ProgramFiles/CLion2023.2.2/my/exe/in.txt&quo......
  • VScode开发STM32笔记(一)生成bin文件
    操作步骤软件条件1、基于stm32-for-vscode工具开发;2、项目处于打开状态,且生成elf文件;具体的安装环境详见文档:https://blog.csdn.net/weixin_42435984/article/details/141894449具体操作1、通过VScode打开对应的STM32项目;2、使用stm32-for-vscode工具对其进行编译;3......
  • 【LeetCode Hot 100】20. 有效的括号
    题目描述这个题目在讲解栈的应用的时候是常用的例子,在遍历括号串的时候维护一个栈结构,如果当前字符是前括号,暂时没有与之配对的后括号,则先将其压入栈中。C++STL和Java都提供了对应的容器,但是由于我们知道栈的大小不可能超过括号串的长度,所以也可以手动用数组模拟,这样运行速度可......
  • VS Code 的SSH连接不成功问题分析与解决
    问题描述:多次输入密码,一直连接不上解决方法;打开远程服务器中~/.vscode-server/bin/xxx文件夹,此时可以看到一个名为vscode-server.tar.gz,截图如下:上面的37开头的文件夹称为CommitId,现在利用CommitID下载远程连接需要的文件。使用这个链接:https://update.code.visualstudio.......
  • leetcode每日一题day15(24.9.25)——公司命名
    思路:首先如果没有相同的后缀,则无论只要不是相同的首字母交换都不会出现重复情况,如果有重复后缀,则还需多增加个不能和,首字符与另一相同后缀字串的首字符相同的字串交换。主要矛盾已经明确,则可对矛盾进行分析。首先把范围缩小到只有两种不同首字母,对于这种情况      ......
  • vscode设置python解释器以及函数无法点击跳转问题
    1.下载插件1.1Python1.2Pylance1.3Remote-SSH2.设置本地/远程python解释器2.1本地设置2-1-1设置解释器路径设置自定义python解释器路径,mac快捷键command+p>python:selectinterpreter选择或者输入解释器2-1-2查看设置结果设置完python-venv路径后,打开......
  • 深入解析:Unicode 与 UTF-8 在 Python 中的秘密武器
    引言字符编码是计算机科学中的一个重要领域,它定义了如何将人类可读的文字转换为机器能够理解的形式。随着互联网的发展,不同的语言和符号需要在全球范围内共享,这就对字符编码提出了更高的要求。Unicode标准就是为了满足这种需求而诞生的,它提供了一套统一的字符集,几乎涵盖了所有现代......
  • Leetcode 622. 设计循环队列
    1.题目基本信息1.1.题目描述设计你的循环队列实现。循环队列是一种线性数据结构,其操作表现基于FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列......
  • Leetcode 706. 设计哈希映射
    1.题目基本信息1.1.题目描述不使用任何内建的哈希表库设计一个哈希映射(HashMap)。实现MyHashMap类:MyHashMap()用空映射初始化对象voidput(intkey,intvalue)向HashMap插入一个键值对(key,value)。如果key已经存在于映射中,则更新其对应的值value。intget(in......