c# - 为什么 task.Wait 不会在非 ui 线程上死锁?还是我只是幸运?

标签 c# asynchronous windows-runtime deadlock async-await

首先是一些背景信息。我正在使现有的 C# 库代码适合在 WinRT 上执行。由于此代码的一小部分深层需要执行一些文件 IO,我们首先尝试保持同步并使用 Task.Wait() 停止主线程,直到所有 IO 完成。

果然,我们很快发现导致死锁。

然后我发现自己更改了原型(prototype)中的大量代码以使其“异步”。也就是说,我插入了 async 和 await 关键字,并相应地更改了方法返回类型。这是很多工作 - 事实上太多毫无意义的工作 - 但我让原型(prototype)以这种方式工作。

然后我做了一个实验,在一个单独的线程上运行带有 Wait 语句的原始代码:

System.Threading.Tasks.Task.Run(()=> Draw(..., cancellationToken)

没有死锁!

现在我很困惑,因为我认为我了解异步编程的工作原理。我们的代码(还)根本没有使用 ConfigureAwait(false)。所以所有 await 语句都应该在它们被调用时的相同上下文中继续。对吗?我假设那意味着:同一个线程。现在,如果该线程调用了“Wait”,这也应该会导致死锁。但事实并非如此。

你们中有人有明确可靠的解释吗?

这个问题的答案将决定我是否真的会通过插入大量有条件的 async/await 关键字来搞乱我们的代码,或者我是否会保持代码干净并只使用一个在这里执行 Wait() 的线程并且那里。如果延续由任意非阻塞线程运行,事情应该没问题。但是,如果它们由 UI 线程运行,如果延续的计算量很大,我们可能会遇到麻烦。

我希望问题是清楚的。如果没有,请告诉我。

最佳答案

我有一个 async/await intro在我的博客上,我在其中准确解释了上下文是什么:

它是 SynchronizationContext.Current,除非它是 null,在这种情况下它是 TaskScheduler.Current。注意:如果当前没有TaskScheduler,那么TaskScheduler.CurrentTaskScheduler.Default一样,都是线程池任务调度器。

在今天的代码中,通常只取决于您是否有一个SynchronizationContext;任务调度程序今天并没有被大量使用(但将来可能会变得更加普遍)。我有一个 article on SynchronizationContext描述了它是如何工作的以及 .NET 提供的一些实现。

WinRT 和其他 UI 框架(WinForms、WPF、Silverlight)都为其主 UI 线程提供 SynchronizationContext。此上下文仅表示单个线程,因此如果您混合使用阻塞代码和异步代码,您很快就会遇到死锁。我更详细地描述了为什么会发生这种情况 in a blog post , 但总的来说它死锁的原因是因为 async 方法试图重新进入它的 SynchronizationContext (在这种情况下,恢复在 UI 线程上执行),但是UI 线程被阻塞,等待 async 方法完成。

线程池没有SynchronizationContext(通常是TaskScheduler)。因此,如果您在线程池线程上执行并阻塞在异步代码上,则不会发生死锁。这是因为捕获的上下文是线程池上下文(不绑定(bind)到特定线程),因此 async 方法可以重新进入其上下文(通过仅在线程池线程上运行),同时另一个线程池线程被阻塞等待它完成。

The answer to this will determine whether I will really go through messing up our code by inserting a lot of conditional async/await keywords, or whether I will keep it clean and just use a thread that does a Wait() here and there.

如果您的代码一直都是async,那么它应该看起来一点也不凌乱。我不确定“条件”是什么意思;我会让它全部 asyncawait 有一个“快速路径”实现,如果操作已经完成,它会同步。

使用后台线程阻塞异步代码是可能的,但它有一些警告:

  1. 你没有 UI 上下文,所以你不能做很多 UI 事情。
  2. 您仍然必须“同步”到 UI 线程,并且您的 UI 线程不应阻塞(例如,它应该 await Task.Run(..),而不是 Task。运行(..)。等待())。对于 WinRT 应用尤其如此。

关于c# - 为什么 task.Wait 不会在非 ui 线程上死锁?还是我只是幸运?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18250710/

相关文章:

c# - 如何在不在 C# 中定义模型类的情况下向现有 json 添加新属性?

c# - 在尝试访问控制的 cs 页面中使用 'this' 时出错

c# - Power Bi 嵌入 token 和错误 PowerBIEntityNotFound

c# - 如何在 Windows Phone 上发出 json 请求时显示加载消息?

c# - Web API 异步,我做对了吗?

xaml - WinRT & XAML - 资源文件但不是本地化的语言?

c# - 尝试创建一个可以写入控制台或表单应用程序的方法

Android异步API设计

javascript - 无法在我的 flipview 中使用鼠标滚轮滚动

c# - FileOpenPicker PickSingleFileAsync 抛出 UnauthorizedAccessException