如果异步等待不创建任何额外的线程,那么它如何使应用程序响应?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (36)

async-await不会创建任何额外的线程,这是没有意义的,因为一台计算机一次不止做一件事情的唯一方法是

  • 实际上一次做多件事情(并行执行,使用多个处理器)
  • 通过调度任务并在它们之间切换来模拟它(做一点点A,一点B,一点点A,等等)。

所以如果async-await这两者都没有,那么它如何使应用程序响应呢?如果只有一个线程,那么调用AnyMethod意味着在执行其他任何操作之前等待方法完成,而该方法中的方法必须等待结果才能继续执行。

提问于
用户回答回答于

在Windows窗体应用程序中处理一个简单的按钮单击事件:

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

在传统的、非异步的世界中,单击事件处理程序的按钮看起来如下所示:

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

当单击表单中的按钮时,应用程序将出现冻结约2秒,而我们则等待此方法完成。所发生的是“消息泵”,基本上是一个循环,被阻塞。

这个循环不断地问windows“有人做了什么事情吗,比如移动鼠标,点击了什么东西?”。用户单击了“tton 1”(或Windows中的等效消息类型),最后调用了button1_Click以上方法。直到这个方法返回,不然这需要2秒时间,在此期间,没有处理任何消息。

大多数处理窗口的事情都是使用消息来完成的,这意味着如果消息循环停止发送消息,哪怕只是一秒钟,用户很快就会注意到它。例如,如果将记事本或任何其他程序移动到自己的程序之上,然后再次离开,则会向程序发送一系列画图信息,指示窗口的哪个区域现在突然又变得可见。

所以,如果在第一个例子中,async/await不创建新线程,它是如何实现的?

好吧,结果是你的方法被分成了两部分。这是一种宽泛的主题类型,所以我不会详细介绍,但只需说一下方法就可以分为以下两部分:

  1. 所有导致await,包括呼吁GetSomethingAsync
  2. 以下所有代码await

说明:

code... code... code... await X(); ... code... code... code...

重新排列:

code... code... code... var x = X(); await X; code... code... code...
^                                  ^          ^                     ^
+---- portion 1 -------------------+          +---- portion 2 ------+

基本上,该方法执行如下:

  1. 它执行所有直到await
  2. 它称之为GetSomethingAsync方法,它执行它的操作,并返回未来将完成2秒的事情到目前为止,我们还在最初的按钮调用中。[医]单击,发生在消息循环调用的主线程上。如果导致await需要很多时间,UI仍然会冻结。在我们的例子中,
  3. await关键字,再加上一些聪明的编译器魔法,基本上是这样的:“好的,你知道吗,我将从这里点击事件处理程序按钮返回。”。当准备完成时,请告诉我,因为我还有一些代码要执行“。
  4. 因此,它返回到Message循环,它现在可以继续释放消息,比如移动窗口、调整大小或单击其他按钮。对于用户来说,UI现在再次响应,处理其他按钮单击、调整大小,最重要的是,所以它似乎不会冻结。
  5. 2秒后,我们等待完成的事情,现在发生的是它将消息放入消息循环正在查看的队列中。
  6. 当消息循环到达该消息时,它将基本上“重新输入”它停止的方法,就在此之后。await并继续执行该方法的其余部分。请注意,此代码再次从消息循环中调用,因此如果此代码碰巧执行了一些冗长的操作而不使用async/await正确地说,它将再次阻塞消息循环。

如果GetSomethingAsync在2秒内完成线程?是的,那么显然有一条新的线索在起作用。然而,这个线程不是。因为这种方法的异步性,是因为该方法的程序员选择了一个线程来实现异步代码。几乎所有异步I/O使用线程,他们使用不同的东西。async/await独自不要旋转新线程,但显然“我们等待的东西”可能是使用线程实现的。

在.NET中,有许多东西不一定是独立的,但仍然是异步的:

  • Web请求(以及许多其他与网络相关的需要时间的事情)
  • 异步文件读写
  • 更多的是,一个好的迹象是,所讨论的类/接口是否有名为SomethingSomethingAsyncBeginSomethingEndSomething还有一个IAsyncResult参与其中。
用户回答回答于

计算机一次做多件事情的唯一方法是(1)一次做多件事情,(2)通过调度任务并在它们之间切换来模拟它。所以如果异步等待没有这两个

记住,await不是使同步代码神奇地异步。是为了让使用我们在调用异步代码时使用的编写同步代码的相同技术。等待是关于使使用高延迟操作的代码看起来像使用低延迟操作的代码,这些高延迟操作可能在线程上,它们可能在特殊用途的硬件上,它们可能将它们的工作撕成小块,并将其放入消息队列中,以便稍后由UI线程处理。他们在做某物实现异步,但是他们就是那些在做这件事的人。等待只是让你利用这个异步。

没有多CPU机器,也没有线程调度程序。你想同时运行两个Windows应用程序。多任务处理合作,操作系统告诉一个进程它可以运行,如果它的行为不正常,它会让所有其他进程都无法得到服务。不得不知道如何在下一次OS手动控制到它的时候找到它停止的地方。单线程异步代码非常类似,使用的是“等待”而不是“屈服”。

调用任何方法都意味着等待方法完成。

这就是异步的本质所在,一个方法返回,它返回一个任务,这意味着“这项工作正在进行中,当它完成时告诉我该做什么”。

扫码关注云+社区