关于UI线程有什么特别之处?

时间:2015-11-15 22:35:32

标签: c# multithreading async-await

假设我有一个同步运行的方法fooCPU(它不会调用执行I / O的纯异步方法,或者通过调用{{1来使用其他线程来运行其代码或类似的方式)。该方法执行一些繁重的计算 - 它的CPU绑定。

现在我在程序中调用Task.Run而不委托它由工作线程执行。如果一行fooCPU需要很长时间才能运行,那么在完成之前不会执行任何其他行。因此,例如,从UI线程调用它会导致UI线程冻结(GUI将无响应)。

当我说 fooCPU是模仿多线程时。两个不同代码的行在一个线程上轮流执行。如果这些行中的一行需要很长时间才能运行,那么在完成之前不会执行任何其他行。

我被告知在UI线程上使用async是正确的,但对于所有其他情况都不是这样(ASP.NET,线程池上的异步,控制台应用程序等等。)

有谁能告诉我这可能意味着什么? UI线程与控制台程序的主线程有何不同?

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

3 个答案:

答案 0 :(得分:10)

这很简单,一个线程一次只能做一件事。因此,如果您在森林中发送UI线程执行非UI相关的操作(例如dbase查询),则所有UI活动都会停止。没有更多的屏幕更新,没有响应鼠标点击和按键。它看起来和行为冻结

你可能会说,"好吧,我只是使用另一个线程来做UI然后"。在控制台模式下工作,有点像。但不是在GUI应用程序中,使代码线程安全很困难,UI根本不是线程安全的,因为涉及的代码太多了。不是你写的那种,你用的类型库包装器。

通用解决方案是反转它,在工作线程上执行非UI相关的东西,并让UI线程只处理简单的UI内容。 Async / await帮助你做到这一点,await右边的内容在一个worker上运行。解决这个问题的唯一方法就是要求UI线程仍然做太多工作。就像每毫秒一次向文本框添加一行文本一样。这只是糟糕的用户界面设计,人类无法快速阅读。

答案 1 :(得分:10)

我建议你阅读我的async intro post;它解释了asyncawait关键字的工作原理。然后,如果您对编写异步代码感兴趣,请继续我的async best practices article

介绍的相关部分:

  

异步方法的开头就像任何其他方法一样执行。也就是说,它会同步运行,直到遇到“await”(或抛出异常)。

所以这就是inner method in your console code example(没有await)同步运行的原因。

  

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

这就是为什么outer method in your console code example(同步的await内部方法)同步运行的原因。

  

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

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

  

究竟是什么“背景”?   简单回答:

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

将所有这些放在一起,您可以将async / await形象化为:此方法分为几个"块",每个await作为分割方法的点。第一个块总是同步运行,并且在每个分割点它可以同步或异步地继续。如果它以异步方式继续,则它将在捕获的上下文中继续(默认情况下)。 UI线程提供了一个上下文,用于执行UI线程上的下一个块。

所以,回答这个问题,关于UI线程的特殊之处在于它们提供了一个SynchronizationContext队列,它将队列工作回到同一个UI线程。

  

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

好吧,Stack Overflow特意打算成为一个论坛;这是一个问题&回答网站。因此,它不是一个要求详尽的教程的地方;当你试图让代码工作时,或者在研究了你可以做的所有事情之后你不理解某些东西时,它就是一个可以来的地方。这就是为什么关于SO的评论被(有目的地)限制的原因 - 它们必须简短,没有良好的代码格式化等。本网站上的评论旨在澄清,而不是作为讨论或论坛帖子。

答案 2 :(得分:3)

鉴于

async void Foo() {
   Bar();
   await Task.Yield();
   Baz();
}

你是对的,如果在UI线程上调用Foo(),则会立即调用Bar(),稍后会调用Baz(),但仍会在UI线程。

但是,这不是线程本身的属性。

实际上正在进行的是这种方法分为类似

的方法
Task Foo() {
   Bar();
   return Task.Yield().Continue(() => {
     Baz();
   });
}

这实际上并不正确,但它的错误方式并不重要。

传递给我假设的Continue方法的参数是可以以某种方式调用以由任务确定的代码。任务可能决定立即执行它,它可能决定在同一个线程的某个稍后点执行它,或者它可能决定在不同的线程上的某个稍后点执行它。

实际上,任务本身并没有决定,他们只是将代表传递给SynchronizationContext。它的同步上下文决定了如何处理要执行的代码。

线程类型之间有什么不同:一旦从线程访问任何 WinForms控件,那么WinForms会为该特定线程安装同步上下文,在同一个线程上稍后安排要执行的代码。

ASP.NET,后台线程,所有不同的同步上下文,以及导致代码调度方式发生变化的原因。