33

我正在阅读http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx?cs-save-lang=1&cs-lang=csharp上的异步函数调用。

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

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();

?

4

2 回答 2

21

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

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

例子:

public void MainFunc()
{
    InnerFunc();
    Console.WriteLine("InnerFunc finished");
}

public void 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
}
于 2013-06-22T11:18:51.583 回答
14

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

嗯,是的,不是的。

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

实际上,它会返回到它的调用方法。要了解返回其调用方法的含义,您可以阅读另一个 C# 编译器魔法 -yield return语句。

迭代器块yield return会将方法分解为状态机 - 语句之后的代码仅在枚举器上调用yield return之后才会执行。MoveNext()(见这个这个)。

现在,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,您的代码调用 this MyMethodAsync()。然后code block 1将在同一个线程上同步运行。
  • SomeAwaitableMethodAsync()也将被同步调用 - 但可以说该方法启动一个新的异步操作并返回一个Task.
  • 这是await进入画面的时候。它将代码流返回给它的调用者,并且线程Thread1可以自由地运行调用者代码。那么在调用方法中发生的事情取决于调用方法await是打开MyMethodAsync()还是做其他事情 - 但重要的Thread1是没有被阻塞。
  • 现在剩下的 await 的魔力 - 当SomeAwaitableMethodAsync()最终返回的任务完成时,code block 2计划运行
  • async/await建立在任务并行库之上——因此,这个调度是通过 TPL 完成的。
  • 现在的问题是,这code block 2可能不会被安排在同一个线程上Thread1,除非它SynchronizationContext具有线程关联的活动(如 WPF/WinForms UI 线程)。await知道SynchronizationContext,因此,在被调用时code block 2被安排在相同SynchronizationContext的 (如果有的话)上。MyMethodAsync()如果没有 active SynchronizationContext,那么很可能code block 2会在一些不同的线程上运行。

最后,我要说的是,由于async/await它是基于编译器创建的状态机,比如yield return,它有一些缺点——例如,你不能await在一个finally块内。

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

于 2013-06-23T08:35:30.263 回答