什么';It’UI线程太特别了
本文关键字:线程 UI It 什么 | 更新日期: 2023-09-27 18:22:10
假设我有一个同步运行的方法fooCPU
(它不调用执行I/O的纯异步方法,也不使用其他线程通过调用Task.Run
或类似方式来运行其代码)。这种方法执行一些繁重的计算——它的CPU限制。
现在,我在程序中调用fooCPU
,而不将其委托给工作线程执行。如果fooCPU
的一行需要很长时间才能运行,则在它完成之前不会执行其他行。例如,从UI线程调用它会导致UI线程冻结(GUI将变得没有响应)。
当我说async/await
是对多语言阅读的模仿。两段不同代码的行在一个线程上轮流执行。如果其中一行需要很长时间才能运行,那么在它完成之前不会执行其他行,
有人告诉我,UI线程上使用的异步是正确的,但并非所有其他情况(ASP.NET、线程池上的异步、控制台应用程序等)都是正确的。
有人能告诉我这可能意味着什么吗?UI线程与控制台程序的主线程有何不同?
我认为没有人希望论坛上的任何人继续讨论相关话题,比如他们出现在评论中,所以最好提出一个新问题。
我建议你阅读我的async
介绍文章;它解释了CCD_ 7和CCD_。然后,如果您对编写异步代码感兴趣,请继续阅读我的async
最佳实践文章。
介绍文章的相关部分:
异步方法的开头和其他方法一样执行。也就是说,它同步运行,直到遇到"等待"(或抛出异常)。
这就是为什么控制台代码示例中的内部方法(没有await
)是同步运行的。
Await检查awaitable,看看它是否已经完成;如果awaitable已经完成,那么该方法就继续运行(同步地,就像常规方法一样)。
这就是为什么控制台代码示例中的外部方法(即await
,它是同步的内部方法)是同步运行的。
稍后,当awaitable完成时,它将执行异步方法的剩余部分。如果您正在等待内置的awaitable(如任务),那么异步方法的其余部分将在返回"await"之前捕获的"上下文"上执行。
这个"上下文"是SynchronizationContext.Current
,除非它是null
,在这种情况下它是TaskScheduler.Current
。或者,更简单的版本:
"语境"究竟是什么?简单答案:
- 如果你在一个UI线程上,那么它就是一个UI上下文
- 如果您正在响应ASP.NET请求,那么它就是ASP.NET请求上下文
- 否则,它通常是一个线程池上下文
将所有这些放在一起,您可以将async
/await
可视化为这样工作:方法被拆分为几个"块",每个await
都充当方法拆分的点。第一个区块总是同步运行,在每个拆分点,它可以同步或异步地继续。如果它以异步方式继续,那么它将在捕获的上下文中继续(默认情况下)。UI线程提供将在UI线程上执行下一个块的上下文。
因此,为了回答这个问题,UI线程的特殊之处在于,它们提供了一个SynchronizationContext
,队列可以返回到同一个UI线程。
我认为没有人希望论坛上的任何人继续讨论相关话题,比如他们出现在评论中,所以最好提出一个新问题。
好吧,Stack Overflow是专门的,而不是旨在成为一个论坛;这是一个问题&回答地点。因此,这不是一个要求详尽教程的地方;当你试图让代码正常工作时,或者在研究了所有关于它的内容后,你不理解某件事时,这是一个可以去的地方。这就是为什么SO上的评论(有目的地)受到限制-它们必须简短,没有漂亮的代码格式等。本网站上的评论旨在澄清,而不是作为讨论或论坛线索。
这很简单,一个线程一次只能做一件事。因此,如果你把你的UI线程发送到树林里做一些与UI无关的事情,比如数据库查询,那么所有的UI活动都会停止。没有更多的屏幕更新,对鼠标点击和按键没有响应。它看起来和行为冻结。
你可能会说,"好吧,那我就用另一个线程来做UI"。在控制台模式下工作。但在GUI应用程序中,使代码线程安全是困难的,并且UI根本不是线程安全的,因为涉及到太多代码。不是你写的那种,而是你用一个漂亮的类库包装器的那种。
通用的解决方案是颠倒这一点,在工作线程上做与UI无关的事情,让UI线程只处理简单的UI事情。Async/await可以帮助您做到这一点,await右边的内容在工作线程上运行。把这件事搞砸的唯一方法是让UI线程仍然做太多的工作,这并不罕见。就像每毫秒向文本框中添加一行文本一样。这只是糟糕的UI设计,人类的阅读速度没有那么快。
给定
async void Foo() {
Bar();
await Task.Yield();
Baz();
}
如果Foo()
在UI线程上被调用,那么Bar()
会立即被调用,Baz()
会在稍后的某个时间被调用,但仍然在UI线程中。
但是,这不是线程本身的属性。
实际情况是,这种方法被分解成类似于的东西
Task Foo() {
Bar();
return Task.Yield().Continue(() => {
Baz();
});
}
这实际上并不正确,但错误的方式并不重要。
传递给我假设的Continue
方法的参数是可以通过某种方式调用的代码,由任务决定。任务可以决定立即执行,也可以决定稍后在同一线程上执行,或者稍后在不同线程上执行。
实际上,任务本身并不能决定,它们只是将委托传递给SynchronizationContext
。正是这种同步上下文决定了如何处理要执行的代码。
这就是线程类型之间的不同:一旦您从一个线程访问任何WinForms控件,WinForms就会为该特定线程安装一个同步上下文,该上下文将调度稍后在同一线程上执行的代码。
ASP.NET、后台线程,都是不同的同步上下文,这就是导致代码调度方式发生变化的原因。