什么';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线程与控制台程序的主线程有何不同?

我认为没有人希望论坛上的任何人继续讨论相关话题,比如他们出现在评论中,所以最好提出一个新问题。

什么';It’UI线程太特别了

我建议你阅读我的async介绍文章;它解释了CCD_ 7和CCD_。然后,如果您对编写异步代码感兴趣,请继续阅读我的async最佳实践文章。

介绍文章的相关部分:

异步方法的开头和其他方法一样执行。也就是说,它同步运行,直到遇到"等待"(或抛出异常)。

这就是为什么控制台代码示例中的内部方法(没有await)是同步运行的。

Await检查awaitable,看看它是否已经完成;如果awaitable已经完成,那么该方法就继续运行(同步地,就像常规方法一样)。

这就是为什么控制台代码示例中的外部方法(即await,它是同步的内部方法)是同步运行的。

稍后,当awaitable完成时,它将执行异步方法的剩余部分。如果您正在等待内置的awaitable(如任务),那么异步方法的其余部分将在返回"await"之前捕获的"上下文"上执行。

这个"上下文"是SynchronizationContext.Current,除非它是null,在这种情况下它是TaskScheduler.Current。或者,更简单的版本:

"语境"究竟是什么?简单答案:

  1. 如果你在一个UI线程上,那么它就是一个UI上下文
  2. 如果您正在响应ASP.NET请求,那么它就是ASP.NET请求上下文
  3. 否则,它通常是一个线程池上下文

将所有这些放在一起,您可以将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、后台线程,都是不同的同步上下文,这就是导致代码调度方式发生变化的原因。