多个等待单个方法

Xen*_*ate 39 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但我正在努力追随当有人打电话时会发生什么SaveAllAsync().

我认为会发生的是:

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

Ste*_*ary 46

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

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

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

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

  • @AndyzSmith `“等待的方法保证有一些时间来完成它们的任务”`不,它们不是。某些地方的某些代码可能拒绝放弃控制并执行阻塞等待,从而阻止这些延续运行。这就是为什么在使用异步代码时“一直异步”很重要。如果某人在某个地方正在对其他事物进行阻塞等待,并且其他事物无法运行,因为“SynchronizationContext”正忙于进行阻塞等待,那么您就遇到了“死锁”。 (2认同)
  • @AndyzSmith:@Servy 是正确的。此外,“调用者最终负责完成每条语句”的说法也是不正确的。您可能想到的是这样的情况:您有一个 UI 线程上下文,并且“async”方法在 UI 线程上恢复。`async` 和 `await` 本身与线程无关,并且在控制台应用程序、ASP.NET 等中工作得很好(具有不同的线程语义)。 (2认同)

Eri*_*ert 24

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

代码块的继续是代码完成后发生的事情.当你遇到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完成线程上完成的,请调用此任务的继续,所以我们再次绕过循环......

合理?

  • @Motig:接下来会发生什么取决于等待任务的上下文。如果它是一个 UI 线程,那么正如我所说,希望触发延续的线程 * 向 UI 线程 * 发送消息。该消息位于队列中,当 UI 线程绕过它时,继续运行。在没有 UI 线程的上下文中,continuation 可以在完成工作的线程上运行,也可以在新的工作线程上运行,或者其他任何东西。与任务关联的上下文决定如何安全地运行延续。 (2认同)