多个等待单个方法

时间:2013-08-26 13:25:20

标签: c# asynchronous async-await

我有这样的方法:

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. 行的来电者
  3. 然后......当他们等待SaveAllAsync()(或等待任务)时......会发生什么?在调用之前,SaveAllAsync()仍然会停留在第一个等待中吗?因为没有涉及线程,我猜是这样的......

4 个答案:

答案 0 :(得分:44)

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

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

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

有关详细信息,我有async/await intro on my blog

答案 1 :(得分:23)

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

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

  

但是当xmlWriter.WriteStartDocumentAsync()的电话已经完成时;怎么了?当前执行是否中断并返回SaveAllAsync()

“完成”这个电话的意思并不清楚。 WriteStartDocumentAsync可能在I / O完成线程上启动异步写入,并返回表示异步工作的Task。等待这项任务做了两件事,就像我说的那样。首先,此任务的继续成为代码的当前位置。其次,控制离开当前方法并运行其他一些代码。在这种情况下,任何名为SaveAllAsync的代码都会运行该调用的延续。

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

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

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

有意义吗?

答案 2 :(得分:0)

每当一个函数'异步'时,意味着当在System.Threading.Tasks.Task上完成'await'时,会发生两件事:

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

  2. 控件返回到:

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

    • 如果它不是async方法中的第一个await,它将返回到执行等待的最后一个Task的“continuation”的任何线程中的任何一个处理程序。

  3. 所以回答:

      

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

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

    但是......如果你或者其他一些代码更深入,那么在await返回的任何任务上执行Task.Result / Task.Wait()会导致事情在“延续”尝试时被卡住在与等待代码相同的线程上运行。

答案 3 :(得分:-2)

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

await SaveAllAsync();
string x = ""; // <- this will run only when SaveAllAsync() is done including all awaits