【问题标题】:Why ThreadPool decides to use the exact context thread which called Task.Wait?为什么 ThreadPool 决定使用名为 Task.Wait 的确切上下文线程?
【发布时间】:2020-07-10 13:02:31
【问题描述】:

为什么 ThreadPool 决定使用名为 Task.Wait 的确切上下文线程?

the question。由于某些原因,我既没有看到问题的评论按钮,也没有看到任何答案的评论按钮。所以,我在一个单独的线程中问了一个相关的问题。

在链接的问题中有一个指向the blog 的答案。根据此博客,以下声明成立。

有代码片段:

// 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;
}

还有死锁发生的解释:

  1. 顶级方法调用GetJsonAsync(在 UI/ASP.NET 上下文中)。
  2. GetJsonAsync 通过调用 HttpClient.GetStringAsync(仍在上下文中)启动 REST 请求。
  3. GetStringAsync 返回未完成的Task,表示 REST 请求未完成。
  4. GetJsonAsync 等待由 GetStringAsync 返回的 Task。上下文被捕获并将用于稍后继续运行GetJsonAsync 方法。 GetJsonAsync返回一个未完成的Task,表示GetJsonAsync方法没有完成。
  5. 顶级方法同步阻塞GetJsonAsync 返回的任务。这会阻塞上下文线程。
  6. ... 最终,REST 请求将完成。这完成了由GetStringAsync 返回的Task
  7. GetJsonAsync 的延续现在已准备好运行,它等待上下文可用,以便在上下文中执行。
  8. 死锁。顶级方法是阻塞上下文线程,等待GetJsonAsync 完成,而GetJsonAsync 正在等待上下文空闲以便它可以完成。

我的问题特别是关于第 7 步。为什么ThreadPool 决定采用一个阻塞的线程并等待它解除阻塞以要求该线程运行代码?为什么不直接拿其他线程呢?

【问题讨论】:

  • “由于某些原因,我没有看到问题和任何答案的评论按钮。” 您还没有足够的声望点。如果你有所收获,你将能够发表评论。应该记录在help center 部分的某处。
  • 问题:这是默认行为,因为通常,您需要从 GUI 线程中异步一些东西,之后,您需要继续使用该 GUI 线程,因为您想要使用异步调用的结果来操作 GUI 元素。请注意,该示例可能是故意使用错误代码编写的。另请注意:您可以在每次通话的基础上关闭此默认行为:Task.ConfigureAwait(bool)
  • @Fildor,所以这里的问题如下。 ThreadPool 可能会尝试决定使用产生任务的同一线程在任务的await 之后继续执行。并且线程池的(系统)配置可能会发生这样的情况,即线程池使用同一线程(即上下文)继续执行比使用非阻塞线程具有更高的优先级。我理解正确吗?
  • 也许“大师”本人可以比我更好地解释这一点:Async and Await - “上下文”部分
  • ThreadPool 不决定与此相关的任何事情。决策由 async-await 机制做出。

标签: c# multithreading task threadpool deadlock


【解决方案1】:

GetJsonAsync 不在任意线程池线程上执行。它在上下文线程上执行。

根据示例代码,任务GetJsonAsync 由按钮单击事件创建,该事件在 UI 线程(上下文线程)上执行。等待任务时,会捕获当前上下文(在本例中为 UI 同步上下文)。任务完成后,继续在同一上下文中继续。

在第7步,任务尝试返回UI线程,但UI线程被.Result阻塞,等待任务返回。于是就发生了死锁。

我注意到引用的问题是关于 ASP.NET WebApi 应用程序的问题。所以只想澄清几点:

ASP.Net WebAPI 确实有一个特殊的同步上下文,但它不同于 UI 上下文。只有一个 UI 线程,因此上下文仅将回调/延续调度到 UI 线程。

但是,ASP.Net WebAPI 的同步上下文不捕获单个线程。 ASP.Net 代码可以在不同/任意线程上运行。上下文负责恢复线程数据并确保以先到先得的方式将延续链接在一起。

【讨论】:

  • 当任务运行时,它会捕获当前上下文。实际上,在等待任务时会捕获上下文。当代码遇到await 关键字时会发生这种情况。
  • 感谢您的宝贵时间。但是你没有回答我的问题。为什么我们不从 ThreadPool 中获取不同的线程来完成方法?是不是因为UI系统被开发为只有一个线程,不能使用其他ThreadPool线程来管理UI组件?
  • @gladtomeetyou 我想你自己回答了这个问题。是的,UI 的线程模型决定 UI 元素只能由创建它们的 UI 线程访问。任务调度程序无法将代码调度到 ThreadPool,因为可能有 UI 访问代码需要 UI 线程。但是,开发人员会知道延续是否需要 UI 线程。在没有的情况下,ConfigureAwait(false) 可用于指示任务可以在线程池线程上恢复。
猜你喜欢
  • 2011-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-23
相关资源
最近更新 更多