Mar*_*ler 6 c# deadlock asynchronous async-await windows-runtime
首先是一小部分背景信息.我正在使现有的C#库代码适合在WinRT上执行.作为这段代码的一小部分内心深处需要做一个小文件IO,我们首先尝试保持同步并使用Task.Wait()来停止主线程直到所有IO完成.
果然,我们很快发现导致陷入僵局.
然后我发现自己在原型中改变了很多代码,使其"异步".也就是说,我插入了异步和等待关键字,我正在相应地更改方法返回类型.这是一项很多工作 - 实际上是太多无意义的工作 - 但是我以这种方式工作了原型.
然后我做了一个实验,我在一个单独的线程上使用Wait语句运行原始代码:
System.Threading.Tasks.Task.Run(()=> Draw(..., cancellationToken)
Run Code Online (Sandbox Code Playgroud)
没有死锁!
现在我很困惑,因为我认为我理解异步编程是如何工作的.我们的代码(还)根本没有使用ConfigureAwait(false).因此,所有等待语句应该在调用它们的相同上下文中继续.对吧?我认为这意味着:同一个线程.现在,如果此线程调用了"Wait",这也会导致死锁.但事实并非如此.
你们中有谁有明确的坚如磐石的解释吗?
这个问题的答案将决定我是否真的会通过插入大量有条件的async/await关键字来搞乱我们的代码,或者我是否会保持它干净并只使用一个在这里和那里执行Wait()的线程.如果延续由任意非阻塞线程运行,那么事情应该没问题.但是,如果它们由UI线程运行,如果延续计算成本很高,我们可能会遇到麻烦.
我希望这个问题很清楚.如果没有,请告诉我.
我在我的博客上有一个async/ await介绍,我在那里准确解释了上下文的含义:
SynchronizationContext.Current除非是,否则就是null这种情况TaskScheduler.Current.注意:如果没有当前TaskScheduler,那么TaskScheduler.Current是相同的TaskScheduler.Default,这是线程池任务调度程序.
在今天的代码中,通常只取决于你是否拥有SynchronizationContext; 任务调度程序今天没有被大量使用(但将来可能会变得更常见).我有一篇文章SynchronizationContext描述了它是如何工作的以及.NET提供的一些实现.
WinRT和其他UI框架(WinForms,WPF,Silverlight)都SynchronizationContext为它们的主UI线程提供了一个.此上下文仅表示单个线程,因此如果混合阻塞和异步代码,则可以快速遇到死锁.我在博客文章中更详细地描述了为什么会发生这种情况,但总的来说,它死锁的原因是因为该async方法试图重新进入它SynchronizationContext(在这种情况下,继续在UI线程上执行),但UI线程是阻止等待该async方法完成.
线程池没有SynchronizationContext(或TaskScheduler通常).因此,如果您在线程池线程上执行并阻塞异步代码,它将不会死锁.这是因为捕获的上下文是线程池上下文(不依赖于特定线程),因此该async方法可以重新进入其上下文(通过仅在线程池线程上运行),而另一个线程池线程被阻塞等待它来完成.
这个问题的答案将决定我是否真的会通过插入大量有条件的async/await关键字来搞乱我们的代码,或者我是否会保持它干净并只使用一个在这里和那里执行Wait()的线程.
如果你的代码一直async都是这样,它根本不应该看起来很乱.我不确定"有条件"是什么意思; 我会做到这一切async.await具有"快速路径"实现,如果操作已经完成,则使其同步.
可以使用后台线程阻止异步代码,但它有一些警告:
await Task.Run(..),而不是Task.Run(..).Wait()).对于WinRT应用程序尤其如此.