async with await与同步调用有何不同?

本文关键字:何不同 调用 同步 with await async | 更新日期: 2023-09-27 18:19:25

我正在阅读异步编程中使用Async和Await的异步函数调用。

在第一个例子中,他们这样做,我得到:

Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
string urlContents = await getStringTask;

但他们解释说,如果在此期间没有任何工作要做,你可以这样做:

string urlContents = await client.GetStringAsync();

据我所知,await关键字将暂停代码流,直到函数返回。那么,这与下面的同步调用有什么不同呢?

string urlContents = client.GetString();

async with await与同步调用有何不同?

调用await client.GetStringAsync()将执行给调用方法,这意味着它不会等待方法完成执行,因此不会阻塞线程。一旦在后台执行完成,该方法将从它停止的地方继续执行。

如果你只调用client.GetString(),线程的执行将不会继续,直到这个方法完成执行,这将阻塞线程,并可能导致UI变得无响应。

的例子:

public void MainFunc()
{
    InnerFunc();
    Console.WriteLine("InnerFunc finished");
}
public async Task InnerFunc()
{
    // This causes InnerFunc to return execution to MainFunc,
    // which will display "InnerFunc finished" immediately.
    string urlContents = await client.GetStringAsync();
    // Do stuff with urlContents
}
public void InnerFunc()
{
    // "InnerFunc finished" will only be displayed when InnerFunc returns,
    // which may take a while since GetString is a costly call.
    string urlContents = client.GetString();
    // Do stuff with urlContents
}

根据我的理解,await关键字将暂停代码流,直到函数返回

好吧,是和不是。

  • 是的,因为代码流在某种意义上确实停止了。不,因为执行这个代码流的线程没有阻塞。(同步调用client.GetString()会阻塞线程)。

实际上,它将返回到它的调用方法。要理解return到它的调用方法是什么意思,你可以阅读另一个c#编译器的魔法——yield return语句。

带有yield return的迭代器块将把方法分解成一个状态机——只有在枚举器上调用MoveNext()之后,yield return语句之后的代码才会执行。(见这个和这个)。

现在,async/await机制也是基于类似的状态机(然而,它比yield return状态机复杂得多)。

为了简化问题,我们考虑一个简单的异步方法:

public async Task MyMethodAsync()
{
    // code block 1 - code before await
    // await stateement
    var r = await SomeAwaitableMethodAsync();
    // code block 2 - code after await
}
  • 当你用async标识符标记一个方法时,你告诉编译器把这个方法分解成一个状态机,并且你要在这个方法中使用await。假设代码运行在一个线程Thread1上,你的代码调用这个线程MyMethodAsync()。然后code block 1将在同一线程上同步运行。
  • SomeAwaitableMethodAsync()也将被同步调用——但是让我们假设该方法启动一个新的异步操作并返回一个Task
  • 这是await进入图片的时候。它将代码流返回给它的调用者,并且线程Thread1可以自由地运行调用者代码。调用方法时发生的情况取决于调用方法await是否在MyMethodAsync()上或做其他事情-但重要的是Thread1不会被阻塞。
  • 现在等待魔法的休息-当SomeAwaitableMethodAsync()返回的任务最终完成时,code block 2调度运行。
  • async/await是建立在任务并行库上的——所以,这个调度是通过TPL完成的。
  • 现在的事情是,这个code block 2可能不会被安排在同一个线程Thread1上,除非它有一个活动的SynchronizationContext与线程的亲和力(如WPF/WinForms UI线程)。awaitSynchronizationContext感知的,因此,当MyMethodAsync()被调用时,code block 2被调度到相同的SynchronizationContext上(如果有的话)。如果没有活动的SynchronizationContext,那么code block 2将在其他线程上运行。

最后,我要说的是,由于async/await是基于由编译器创建的状态机,就像yield return一样,它有一些缺点——例如,你不能在finally块中使用await

我希望这能消除你的疑虑。