再次解释异步等待

本文关键字:等待 异步 解释 | 更新日期: 2023-09-27 18:16:01

这是我的事件处理程序代码:

protected async void TestrunSaveExecute()
{
    bool saveResult = await SaveTestRunAsync();
}

为了保持UI的响应性,我使用了async/await方法。

据我所知,我现在可以在不阻塞UI的情况下在SaveTestRunAsync()中执行一些冗长的操作,因为它是通过使用await关键字解耦的。

private async Task<bool> SaveTestRunAsync()
{
    //System.Threading.Thread.Sleep(5000); --> this blocks the UI
    await Task.Delay(5000); // this doesn't block UI
    return true;
}

你能解释一下为什么对Thread.Sleep的调用仍然会阻塞UI,而Task.Delay没有吗

再次解释异步等待

代码仍在UI线程上运行。

它没有在后台线程上运行。

因此,您在该异步方法中执行的任何冗长、昂贵的操作仍将在该时间段内阻塞UI。

Thread.Sleep使UI线程进入睡眠状态。

您需要了解asyncawait在这种情况下是如何工作的。

这里的await基本上是这样说的:

让我们把这个方法一分为二。第一部分是执行到CCD_ 11的点的任何内容。第二部分是在可放弃对象完成之后应该执行的内容。

因此,基本上,该方法一直执行到达到await Task.Delay(5000);。然后,它将"5秒的延迟"发挥作用,并表示"安排完成后执行其余代码"。然后它返回,以便UI线程可以继续发送消息。

5秒钟结束后,UI线程将执行该方法的其余部分。

基本上,如果您执行异步I/O,这是好的,但如果您执行成本高昂的操作,如处理大型数据集或类似操作,这就不那么好了。

那你怎么能做到呢?

您可以使用Task.Run

这将启动另一个线程来执行它所得到的委托,同时UI线程可以自由地做其他事情。

基本上,你可以把使用await的方法想象成这样:

Life of method:    <------------------------------------------------------->
Parts:             [ start of method ----][awaitable][ rest of method -----]

因此,该方法将执行第一部分,直到它到达await X,然后它将检查X是否已经完成,如果还没有完成,它将设置一些Task对象,这样awaitable对象就可以运行,一旦完成,"方法的其余部分"就可以运行。

如果X已经完成,也许它是一个已经完成的异步I/O操作,或者它完成得非常快,那么该方法将继续执行该方法的其余部分,就像您没有在那里编写await一样。

但如果没有,它就会返回。这一点很重要,因为它可以让UI线程(在本例中(重新开始从用户那里抽取消息,比如鼠标点击和其他东西。

一旦等待"可放弃对象"的任务被告知可放弃对象已完成,"方法的其余部分"就会被调度,这基本上(在这种情况下(会将一条消息放入消息队列,要求它执行该方法的剩余部分。

在这样的方法中,await语句越多,片段就越多,基本上它只会将方法拆分为更多的部分。

您可以使用早期.NET版本中引入的Task类来完成所有这些操作。async/await的全部目的是使编写代码变得更容易,因为将代码封装在任务对象中会产生将代码从内到外的不幸效果,并使处理异常和循环等问题变得困难。

据我所知,我现在可以在SaveTestRunAsync((而不阻塞UI,因为它通过使用await关键字。

async/await并不意味着您可以在不阻塞UI的情况下执行"长时间操作"。相反,当您使用await时,您不会在内部排队处理不同线程上的工作,您的代码会继续在同一个线程(在您的情况下是UI线程(上执行,直到到达内部await并返回调用方。

Thread.SleepTask.Delay之间的区别在于,前者是一个阻塞调用,它将暂停UI线程,直到指定的时间结束。后者将在内部使用Timer并将控制权交还给调用方法。一旦计时器的时钟过去,它将在停止的地方恢复执行(这就是编译器魔力的作用所在(

Await不创建线程或任务,它"只是"调用等待中的函数(不应该阻塞,否则调用方将被阻塞,因为这仍然是同步的(,要求编译器为等待中函数返回的任务生成一个延续,包含等待后代码的"其余部分",并将新任务返回给调用者(如果等待函数返回任务(。当延续完成时,该任务将被通知为完成。

该框架确保延续将在UI线程上运行(如果应用程序有(,如果您希望能够从异步函数访问UI元素,这是必须的。

因此,异步函数不应该包含阻塞调用。阻塞调用应该使用task"包装"在任务中。Run((

Async/await是比线程更高级别的抽象。

实际的实现可能使用工作线程来实现异步,但它可能(而且经常(在同一线程中使用其他同步技术。

因此,如果异步/等待的当前实现不是基于线程的,那么对Thread.Sleep的调用将阻塞UI线程。

相关阅读:这一切都与同步上下文有关.