如果异步的await不会产生任何额外的线程,那么它是如何让应用程序响应? [英] If async-await doesn't create any additional threads, then how does it make applications responsive?

查看:112
本文介绍了如果异步的await不会产生任何额外的线程,那么它是如何让应用程序响应?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一次又一次,我看到它说,使用异步 - 等待不会产生任何额外的线程。这是没有意义的,因为计算机会出现唯一途径在同一时间做超过1件事就是


  • 实际上做了1个多件事的时间(并行执行,充分利用多处理器)

  • 将计划任务模拟它和它们之间的切换(做的A,B,A的一点点,等一点点一点点。)

所以,如果异步 - 等待既不能的,那么怎么能够让一个应用程序响应?如果只有1个线程,然后调用任何方法手段等方法在做任何事情之前完成,该方法内部的方法有继续之前等待结果,等等。


解决方案

其实,异步/的await是不是那么神奇。完整的话题相当广泛,但对于一个快速而又完备足以回答你的问题,我认为我们可以管理。

让我们来解决在Windows一个简单的按钮单击事件窗体应用程序:

 公共异步无效的button1_Click(对象发件人,EventArgs的发送)
{
    Console.WriteLine(等待之前);
    等待GetSomethingAsync();
    Console.WriteLine(后等待);
}

我要的明确不会说说不管它是什么 GetSomethingAsync 正在恢复现在。让我们只说这是一件会后完成的,说,2秒。

在一个传统的,非异步的,世界的,你的按钮单击事件处理会是这个样子:

 公共无效的button1_Click(对象发件人,EventArgs的发送)
{
    Console.WriteLine(前等待);
    DoSomethingThatTakes2Seconds();
    Console.WriteLine(之后等待);
}

当您单击窗体按钮,应用程序会出现冻结约2秒钟,在我们等待这个方法来完成。什么情况是,消息泵,基本上是一个循环,不断地询问窗口有没有人做了什么?如果是的话,告诉我!然后将处理东西,现在停留等待按钮点击处理完成。这需要2秒,而这是在等待,消息循环是不抽的消息。

这与Windows处理大部分事情正在使用的消息,这意味着如果消息循环停止抽水的消息,哪怕只是一秒钟,它是由用户快速完成引人注目。

所以,如果在第一个例子中,异步/的await 不产生新的线程,它是如何做到的?

那么,什么情况是,你的方法被一分为二。这是这些广泛的主题类型的事情,所以我不会去考虑太多细节,但足以说该方法被分成这两件事之一:


  1. 所有code领导到等待

  2. 所有code以下的await

基本上执行的方法是这样的:


  1. 这一切都执行到等待

  2. 它调用 GetSomethingAsync 方法,它做它的东西,并返回的东西,将在未来

    到目前为止,我们还是原来的呼叫的button1_Click里面,发生在主线程,消息循环呼吁。如果code领导到等待花费了大量的时间,用户界面​​将仍然冻结。在我们的例子,没有那么多的


  3. 什么的await 关键字,有一些聪明的编译器魔术一起,确实是它基本上像好吧,你知道吗,我要去简单地从按钮单击事件处理回到这里。当你(在,我们等待的东西)去接近完成,让我知道,因为我仍然有一些code留下来执行。

    其实这会让<一个href=\"https://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext(v=vs.110).aspx\">SynchronizationContext类知道它完成,这取决于那就是在场上,现在的实际同步情况下,将排队执行。在Windows窗体程序中使用的上下文类将使用该消息循环抽了排队长龙吧。


  4. 因此​​,返回到消息循环,这是现在可以自由地继续抽的消息,像移动窗口,调整其大小,或单击其他按钮。

    对于用户来说,用户界面​​是现在再次响应,处理其他按钮点击,缩放,最重要的是,重绘,所以它不会出现冻结。


  5. 2秒后,我们正在等待完成并现在会发生什么是它(当然,同步上下文)放置一个消息进入该消息循环看着排队,说:喂的东西,我得到了一些更多code为你执行,这code是所有code的之后的的游览车。

  6. 当消息循环获取到该消息,则基本上是重新进入这种方法离开的地方,就在等待,并继续执行的其余方法。请注意,这code再次从消息循环调用,所以如果这code恰好做一些冗长而无需使用异步/的await 得当,它将再次阻止消息循环

有许多移动部件引擎盖下这里,所以这里有一些详细信息的链接,我会说:如果你需要它,但这个话题的的相当广泛,它是相当重要的是要知道的部分的运动部件的的。总是你要明白,异步/等待仍然是一个漏的概念。一些潜在的局限和问题仍然高达泄漏到周围的code,如果他们不这样做,你通常最终不得不调试随机打破了看似毫无理由的申请。


确定,因此,如果 GetSomethingAsync 旋转起来一个线程,将在2秒钟内完成怎么办?是的,那显然没有在玩一个新的线程。此线程,但是,是不是的,因为的这种方法的异步岬的,这是因为这种方法的程序员选择一个线程来实现异步code。几乎所有的异步I / O的的使用线程,它们使用了不同的东西。 异步/的await 自己的不旋转起来新主题,但显然的事情,我们等待可以使用线程来实现。

有.NET中很多东西不一定旋转了一个线程对自己,但仍异步的:


  • Web请求(以及其他许多与网络相关的事情,需要时间)

  • 异步文件读取和写入

  • 更多的人,一个好兆头是,如果有问题的类/接口已经命名的方法 SomethingSomethingAsync BeginSomething EndSomething 并有一个的IAsyncResult 参与。

通常,这些东西不使用引擎盖下一个主题。


确定,所以你需要那个广泛的话题东西?

好吧,让我们问尝试罗斯林我们按一下按钮:

<一个href=\"http://tryroslyn.azurewebsites.net/#K4Zwlgdg5gBAygTxAFwKYFsDcAoUlaIoYB0AKgBYBOqAhgCb5k0gDWIO2ADsAEYA2YAMYxBfZiBgBhGAG9sMBTG78hMAG4B7MHRgBZABQBKWfMUBfUwstLeA4cwQRhm7TB7BkyDRACMAfUk7Fn0NHgArVEFkGBBUCDpUSgAaGABRNTjkAEFKKAlUQ2s5RRKpbxANPlRiAHVKMDQAGUhUfQAiHlQAMw1qGBoAdxoG/DbDHFLFQeHogHFUZDgNdAXyfCyQR0EjCcmyiAqq2vqmlvaaLrRKfqGR6DHdhQsS62U7GFJmFhh5xeXV9abJxGIrWErUZDASgQD5fYgAEVQYgQ+gATAAGTHjawWMxAA=\">Try罗斯林

我不打算在完全生成的类链接在这里,但它是pretty血淋淋的东西。

Time and time again, I see it said that using async-await doesn't create any additional threads. That doesn't make sense because the only ways that a computer can appear to be doing more than 1 thing at a time is

  • Actually doing more than 1 thing at a time (executing in parallel, making use of multiple processors)
  • Simulating it by scheduling tasks and switching between them (do a little bit of A, a little bit of B, a little bit of A, etc.)

So if async-await does neither of those, then how can it make an application responsive? If there is only 1 thread, then calling any method means waiting for the method to complete before doing anything else, and the methods inside that method have to wait for the result before proceeding, and so forth.

解决方案

Actually, async/await is not that magical. The full topic is quite broad but for a quick yet complete enough answer to your question I think we can manage.

Let's tackle a simple button click event in a Windows Forms application:

public async void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine("before awaiting");
    await GetSomethingAsync();
    Console.WriteLine("after awaiting");
}

I'm going to explicitly not talk about whatever it is GetSomethingAsync is returning for now. Let's just say this is something that will complete after, say, 2 seconds.

In a traditional, non-asynchronous, world, your button click event handler would look something like this:

public void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine("before waiting");
    DoSomethingThatTakes2Seconds();
    Console.WriteLine("after waiting");
}

When you click the button in the form, the application will appear to freeze for around 2 seconds, while we wait for this method to complete. What happens is that the "message pump", basically a loop, that continuously asks windows "Has anyone done something? If so, tell me!" and then processes that "something" is now stuck waiting for the button click processing to finish. This takes 2 seconds and while this is waiting, the message loop is not pumping messages.

Most things that deal with windows are done using messages, which means that if the message loop stops pumping messages, even for just a second, it is quickly noticeable by the user.

So, if in the first example, async/await doesn't create new threads, how does it do it?

Well, what happens is that your method is split into two. This is one of those broad topic type of things so I won't go into too much detail but suffice to say the method is split into these two things:

  1. All the code leading up to await
  2. All the code following await

Basically the method executes like this:

  1. It executes everything up to await
  2. It calls the GetSomethingAsync method, which does its thing, and returns something that will complete 2 seconds in the future

    So far we're still inside the original call to button1_Click, happening on the main thread, called from the message loop. If the code leading up to await takes a lot of time, the UI will still freeze. In our example, not so much

  3. What the await keyword, together with some clever compiler magic, does is that it basically something like "Ok, you know what, I'm going to simply return from the button click event handler here. When you (as in, the thing we're waiting for) get around to completing, let me know because I still have some code left to execute".

    Actually it will let the SynchronizationContext class know that it is done, which, depending on the actual synchronization context that is in play right now, will queue up for execution. The context class used in a Windows Forms program will queue it using the queue that the message loop is pumping.

  4. So it returns back to the message loop, which is now free to continue pumping messages, like moving the window, resizing it, or clicking other buttons.

    For the user, the UI is now responsive again, processing other button clicks, resizing and most importantly, redrawing, so it doesn't appear to freeze.

  5. 2 seconds later, the thing we're waiting for completes and what happens now is that it (well, the synchronization context) places a message into the queue that the message loop is looking at, saying "Hey, I got some more code for you to execute", and this code is all the code after the await.
  6. When the message loop gets to that message, it will basically "re-enter" that method where it left off, just after await and continue executing the rest of the method. Note that this code is again called from the message loop so if this code happens to do something lengthy without using async/await properly, it will again block the message loop

There are many moving parts under the hood here so here are some links to more information, I was going to say "should you need it", but this topic is quite broad and it is fairly important to know some of those moving parts. Invariably you're going to understand that async/await is still a leaky concept. Some of the underlying limitations and problems still leak up into the surrounding code, and if they don't, you usually end up having to debug an application that breaks randomly for seemingly no good reason.


OK, so what if GetSomethingAsync spins up a thread that will complete in 2 seconds? Yes, then obviously there is a new thread in play. This thread, however, is not because of the async-ness of this method, it is because the programmer of this method chose a thread to implement asynchronous code. Almost all asynchronous I/O don't use a thread, they use different things. async/await by themselves do not spin up new threads but obviously the "things we wait for" may be implemented using threads.

There are many things in .NET that do not necessarily spin up a thread on their own but are still asynchronous:

  • Web requests (and many other network related things that takes time)
  • Asynchronous file reading and writing
  • and many more, a good sign is if the class/interface in question has methods named SomethingSomethingAsync or BeginSomething and EndSomething and there's an IAsyncResult involved.

Usually these things do not use a thread under the hood.


OK, so you want some of that "broad topic stuff"?

Well, let's ask Try Roslyn about our button click:

Try Roslyn

I'm not going to link in the full generated class here but it's pretty gory stuff.

这篇关于如果异步的await不会产生任何额外的线程,那么它是如何让应用程序响应?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆