2015-11-15 41 views
6

比方说,我有一个同步运行的方法fooCPU(它不会调用执行I/O的纯异步方法,或通过调用Task.Run或类似的方法使用其他线程来运行其代码)。该方法执行一些繁重的计算 - 它受CPU限制。UI线程有什么特别之处?

现在我在我的程序中调用fooCPU而不委托它由工作线程执行。如果一行fooCPU将花费很长时间运行,那么在完成之前不会执行其他行。例如,从UI线程调用它会导致UI线程冻结(GUI将变为无响应)。

当我说async/await是一个模仿多线程。两条不同代码的代码在单个线程中轮流执行。如果其中一条线路需要很长时间才能运行,则不会执行其他线路,直至完成。,

我已经被告知在UI线程中使用async是正确的,但对于所有其他情况(ASP.NET,线程池上的异步,控制台应用程序等)并非如此。

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

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

+0

我会在win窗体应用程序和控制台应用程序上编译相同的代码,并使用ildasm或反射器检查编译的IL的差异以查看我自己。 –

+1

@OguzOzgul这对许多问题很有用,但不是这个 - 代码完全一样。全局状态有哪些变化 - 同步上下文的存在。 – Luaan

+1

答案相当不错,但唯一能满足你好奇心的就是明白等待的细节。搜索“.net等待内部”看起来很不错。如果你有一个小时的时间,这将会回答一切,我希望。 – usr

回答

10

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

开场后的相关部分:

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

所以这就是为什么inner method in your console code example(没有await)同步运行。

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

因此,这就是为什么outer method in your console code example(这是await荷兰国际集团内方法这是同步的)被同步运行。

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

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

究竟是什么“上下文”? 答案很简单:

  1. 如果你在一个UI线程,那么它是一个UI背景。
  2. 如果您对ASP.NET请求做出响应,那么这是一个ASP.NET请求上下文。
  3. 否则,它通常是一个线程池上下文。

把所有这一切放在一起,你可以想像async/await像这样的工作:方法被分为若干“块”,每个await充当地步方法是分裂的。第一个块始终同步运行,并且在每个分割点可以同步或异步继续。如果它异步继续,那么它将继续在捕获的上下文中(默认情况下)。 UI线程提供将执行UI线程上下一个块的上下文。

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

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

那么,堆栈溢出具体是不是打算成为一个论坛;这是一个问题&答案网站。所以它不是要求详尽教程的地方;当你试图让代码工作,或者在研究了你所能做的所有事情之后,你不明白某些东西时,这是一个地方。这就是为什么对SO的评论(有目的地)受到限制 - 它们必须简短,没有很好的代码格式等。本网站的评论意在澄清,而不是讨论或论坛帖子。

+0

谢谢。我实际上已经阅读了一些msdn博客和你的博客文章。 'UI线程提供了一个将在UI线程上执行下一个块的上下文' - 它不遵循你的介绍文章中关于异步/等待的内容,是不是?我认为上下文不是线程的标识符(许多线程可以共享一个上下文)。关于“这个背景”究竟是什么?简单的回答:如果你在一个UI线程上,那么这是一个UI上下文。' - 它不能解释_context_的真实含义。 – user4205580

+0

所以我猜'UI线程提供了一个上下文,它将执行UI线程中的下一个块'来自[this](https://msdn.microsoft.com/en-us/magazine/gg598924.aspx),引用: _当前实现为每个UI线程创建一个WindowsFormsSynchronizationContext。 – user4205580

+0

@ user4205580:在介绍文章中,在“简单”解释之后的下一段中:'如果SynchronizationContext.Current不为null,则它是当前的SynchronizationContext。 (UI和ASP.NET请求上下文是SynchronizationContext上下文)。' –

3

鉴于

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,后台线程,它的所有不同的同步环境,这是什么造成的代码如何被安排的变化。

9

这很简单,线程一次只能做一件事。因此,如果您将UI线程发送到与UI无关的树林中,比如说一个dbase查询,那么所有的UI活动都会停止。没有更多的屏幕更新,没有响应鼠标点击和按键。它看起来和行动冷冻

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

通用的解决方案是反转,做一个工作线程上的非UI相关的东西,并离开UI线程只处理简单的用户界面的东西。异步/等待可以帮助你做到这一点,什么是在等待工人的权利运行。唯一的办法就是搞砸了,而且这种情况并不少见的是要求UI线程仍然做了太多的工作。就像每毫秒向文本框添加一行文本一样。这只是糟糕的UI设计,人类不会那么快读。

+0

'在工作者上等待运行的权利' - 我想我已经阅读了很多答案(这里也是如此),指出“await”本身不会创建或切换线程。通过'await syncFoo()'调用一个同步方法不会让它在另一个线程上执行。实际上,'syncFoo()'和'await syncFoo()'没有区别。 – user4205580

+0

当然,事实并非如此。但是他的正确思想中没有人会把任何东西放在不同步的右边。等待肯定会帮助任何人获得正确的思想,而不是一切都在那里。 –

+0

对不起,也许我错了,但我认为即使方法是异步的,异步本身也不会让它在工作线程上执行 - 这取决于该方法是否调用例如“Task.Run”。纯异步方法通常不使用其他线程,它们在[比线程更低的层次上执行I/O操作(http://blog.stephencleary.com/2013/11/there-is-no-thread.html) 。 – user4205580

相关问题