60

我有这样的方法:

public static async Task SaveAllAsync()
{
    foreach (var kvp in configurationFileMap)
    {
        using (XmlWriter xmlWriter = XmlWriter.Create(kvp.Value, XML_WRITER_SETTINGS))
        {
            FieldInfo[] allPublicFields = 
                           kvp.Key.GetFields(BindingFlags.Public | BindingFlags.Static);
            await xmlWriter.WriteStartDocumentAsync();
            foreach (FieldInfo fi in allPublicFields)
            {
                await xmlWriter.WriteStartElementAsync("some", "text", "here");
            }
            await xmlWriter.WriteEndDocumentAsync();
        }
    }
}

但我很难理解当有人打电话时会发生什么SaveAllAsync()

我认为会发生的是:

  1. 当有人第一次调用它时,SaveAllAsync()会将控制权返回给该线路的调用者await xmlWriter.WriteStartDocumentAsync();
  2. 然后......当他们等待SaveAllAsync()(或等待任务)......会发生什么?仍然会SaveAllAsync()停留在第一个 await 直到被调用?因为没有涉及线程,我想是这样的......
4

4 回答 4

60

您可以将方法await视为“暂停”该async方法,直到该操作完成。作为一种特殊情况,如果操作已经完成(或非常快),则await不会“暂停”该方法;它将立即继续执行。

因此在这种情况下(假设WriteStartDocumentAsync尚未完成),await将暂停该方法并将未完成的任务返回给调用者。请注意,方法Task返回的async代表该方法;当方法完成时,即Task完成。

最终,WriteStartDocumentAsync将完成,这将安排async方法的其余部分继续运行。在这种情况下,它将执行该方法的下一部分,直到下一个await,当它再次暂停时,等等。最终,该async方法将完成,这将完成Task返回以表示该方法。

有关更多信息,我的博客上有一个async/await介绍

于 2013-08-26T13:52:54.557 回答
27

斯蒂芬斯的回答当然是正确的。这是另一种思考方式,可能会有所帮助。

一大块代码的延续是代码完成后发生的事情。当你打了await两件事发生。首先,执行中的当前位置成为等待任务的延续。其次,控制离开当前方法并运行其他一些代码。其他代码可能是第一次调用的延续,或者可能是完全不同的东西,例如事件处理程序。

但是当调用xmlWriter.WriteStartDocumentAsync()完成时;怎么了?当前执行是否中断并返回到SaveAllAsync()?

目前尚不清楚您所说的“完成”是什么意思。WriteStartDocumentAsync启动异步写入,可能在 I/O 完成线程上,并返回Task代表异步工作的 a。正如我所说,等待该任务会做两件事。首先,这个任务的延续变成了代码的当前位置。其次,控制离开当前方法并运行其他一些代码。在这种情况下,任何被调用的代码SaveAllAsync都会运行该调用的延续。

现在让我们假设代码 - 的调用者SaveAllAsync继续运行,并进一步假设您处于具有 UI 线程的应用程序中,例如 Windows 窗体应用程序或 WPF 应用程序。现在我们有两个线程:UI 线程和一个 IO 完成线程。UI 线程正在运行SaveAllAsync最终返回的调用者,现在 UI 线程只是坐在那里处理 Windows 消息以触发事件处理程序的循环。

最终 IO 完成并且 IO 完成线程向 UI 线程发送一条说明“您现在可以运行此任务的延续”。如果 UI 线程忙,则该消息将排队;最终 UI 线程到达它并调用延续。控制在第一个 之后恢复await,然后您进入循环。

现在WriteStartElementAsync被调用。它再次启动一些代码运行,这取决于 IO 完成线程上发生的事情(大概;它的工作方式取决于它,但这是一个合理的猜测),返回一个Task表示该工作的代码,并且 UI 线程等待任务。同样,执行中的当前位置被注册为该任务的延续,并且控制权返回给调用第一个延续的调用者——即 UI 线程的事件处理器。它继续愉快地处理消息,直到有一天 IO 线程向它发出信号并说嘿,你要求的工作在 IO 完成线程上完成,请调用此任务的延续,所以我们再次循环......

说得通?

于 2013-08-26T14:27:06.197 回答
0

每当一个函数是“异步”时,这意味着当在 System.Threading.Tasks.Task 上完成“等待”时,会发生两件主要事情:

  1. 执行中的当前位置成为等待任务的“延续”,这意味着在任务完成后,它将执行必要的操作以确保调用异步方法的其余部分。这项工作可以在特定线程、某个随机线程池线程或 UI 线程中完成,这取决于任务为“继续”而获得的 SynchronizationContext。

  2. 控制权返回到:

    • 如果它是异步方法中的第一个等待,它将返回原始调用方法,并使用 System.Threading.Tasks.Task 表示异步方法(不是在异步方法中创建的任何任务)。它可以忽略它并继续执行,使用 Task.Result/Task.Wait() 等待它(注意不要阻塞 UI 线程),或者如果它本身是异步方法,甚至对其执行等待。

    • 如果它不是异步方法中的第一个等待,它只会返回到正在执行等待的最后一个任务的“继续”的线程中的任何处理程序。

所以回答:

当他们等待 SaveAllAsync() (或等待任务)......会发生什么?

执行 await SaveAllAsync() 不一定会导致它卡在任何内部等待上。这是因为 SaveAllAsync() 上的等待只是退回到调用者调用 SaveAllAsync() 的任何方法,它可以像什么都没发生一样继续,就像 SaveAllAsync() 的内部等待退回到它一样。这使线程保持移动并能够(可能)在稍后的某个时间响应请求以运行 SaveAllAsync() 的第一个内部等待的“继续”:等待 xmlWriter.WriteStartDocumentAsync()'。这样,SaveAllAsync() 将一点一点地完成,不会卡住。

但是......如果您或其他一些更深入的代码曾经对等待返回的任何任务执行 Task.Result/Task.Wait() 如果“继续”尝试在与等待代码相同的线程。

于 2017-05-29T19:37:14.337 回答
-1

使用父方法示例的简单答案:

await SaveAllAsync();
string x = ""; // <- this will run only when SaveAllAsync() is done including all awaits
于 2020-02-29T17:39:27.770 回答