5

在玩过 .NET 4.5 async\await 框架后,我有一个问题。

让我们看一下这个程序(msdn 示例):

    async Task<int> AccessTheWebAsync()
    {
        HttpClient client = new HttpClient();

        Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

        DoIndependentWork();

        string urlContents = await getStringTask;

        return urlContents.Length;
    }


    void DoIndependentWork()
    {
        resultsTextBox.Text += "Working . . . . . . .\r\n";
    }

该程序将按以下顺序运行:

  1. 新的 HttpClient。
  2. GetStringAsync 同步调用。
  3. 在某些时候,GetStringAsync 调用 await 并且控件返回到 AccessTheWebAsync。
  4. 调用了 DoIndependentWork。
  5. 程序等待返回的字符串(如果操作未完成则阻塞)。
  6. 返回 urlContent 的长度。

我花了一段时间才理解的一件事是,GetStringAsync尽管它的名称该方法是同步运行的(名称约定确实具有误导性)。

为了异步运行该方法,我们需要显式使用Task.Runor Task.Factory.StartNew

但真正的问题是,如果我们有独立的工作,为什么不马上做,而不是等待从 GetStringAsync 调用 await?(换句话说,为什么 async 方法不能按照定义异步运行?)

编辑: 我将改写第二个和第三个操作:

(2) GetStringAsync 同步启动。

(3) 在某些时候,GetStringAsync 调用 await 并且线程分叉,控制返回到 AccessTheWebAsync。

4

4 回答 4

8

我花了一段时间才理解的一件事是,尽管 GetStringAsync 方法的名称是同步运行的(名称约定确实具有误导性)。

这是不正确的。 GetStringAsync返回一个Task<string>。它将立即返回,这意味着DoIndependentWork将(可能)在下载完成之前运行。运算符将await异步等待,直到Task<T>返回的 byGetStringAsync完成。

但真正的问题是,如果我们有独立的工作,为什么不马上做,而不是等待从 GetStringAsync 调用 await?(换句话说,为什么 async 方法不能按照定义异步运行?)

异步方法的实际“工作”在方法返回后运行。返回的Task那将(通常)处于未完成的状态,这意味着该方法仍在异步运行。您可以检查Task.IsCompleted以验证。

尝试这个:

Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
Debug.WriteLine("Completed? {0}", getStringTask.IsCompleted); // Will likely print false
DoIndependentWork();
string urlContents = await getStringTask;
Debug.WriteLine("Completed now? {0}", getStringTask.IsCompleted); // Will always print true
于 2013-10-19T00:19:31.013 回答
3

每个async方法在每个await. 每个段将成为编译器生成的状态机上的一个状态。

每条await指令都作用于a是最常见的情况的awaitable 。Task

每个状态/段将同步执行,直到检查接收到的可等待对象是否已经完成。

如果 awaitable 完成,它将在下一个状态继续执行。

如果 awaitable 未完成且没有 current SynchronizationContext,则执行将被阻止,直到 awaitable 完成,此时将开始执行下一个 state。

如果当前SynchronizationContext存在,则执行将返回给调用者,并且当等待完成时,继续到下一个状态将被发布到捕获的SynchronizationContext.

于 2013-10-20T20:40:35.227 回答
2

程序等待返回的字符串(如果操作未完成则阻塞)。

await不阻止await getStringTask。它将AccessTheWebAsync方法的内部状态(局部变量和执行点所在的位置)保存在编译器生成的状态机对象中,并将返回到调用的外部方法AccessTheWebAsync。当任务完成时,状态将被恢复,并且稍后将getStringTask通过编译器生成的延续回调异步恢复执行。如果您熟悉 C# 迭代器和yield关键字,await状态机控制流程与它非常相似,尽管迭代器是同步执行的。

之后如何恢复执行await取决于启动await. 如果它是一个 UI 线程泵送 Windows 消息,则可能会在线程的消息循环的下一次迭代(通常在内部Application.Run)调用延续回调。如果它是非 UI 线程(例如,控制台应用程序),则可能会在不同的线程上继续进行。因此,虽然您的方法的控制流在逻辑上保持线性,但从技术上讲并非如此(如果您只是这样做getStringTask.Wait()而不是await getStringTask.

于 2013-10-19T00:28:02.740 回答
2

程序等待返回的字符串(如果操作未完成则阻塞)。

不,它没有。await 等待,但它不会阻塞任何线程。这就是重点。

我花了一段时间才理解的一件事是,GetStringAsync尽管它的名称该方法是同步运行的(名称约定确实具有误导性)。

那是错误的。大多数方法确实是异步运行的。它很可能在开始时也有一个小的同步部分,但这应该可以忽略不计。

为了异步运行该方法,我们需要显式使用Task.Runor Task.Factory.StartNew

不,该方法已经异步运行。如果您想在另一个线程上运行小的同步部分,那么您可以使用Task.Run(),但这几乎没有任何意义。另外,不要使用StartNew()它,它不适用于异步方法。

换句话说,为什么 async 方法不能按照定义异步运行?

关于它的重要一点await是它在它离开的相同上下文中恢复。

这在 GUI 应用程序中非常有用,您经常希望修改 UI,然后执行异步操作,然后再次修改 UI。如果async自动意味着整个方法在ThreadPool线程上执行,那么这是不可能的。

此外,这样做效率更高,因为您不必为花费很少时间的事情切换到另一个线程。

于 2013-10-19T01:06:10.323 回答