Подтвердить что ты не робот

Несколько ожиданий в одном методе

У меня есть такой способ:

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

Я думаю, что это произойдет:

  • Когда кто-то сначала вызовет его, SaveAllAsync() вернет управление вызывающему абоненту в строке await xmlWriter.WriteStartDocumentAsync();
  • Затем... Когда они ждут SaveAllAsync() (или ждут задания)... Что происходит? Будет ли еще SaveAllAsync() застрять на первом ожидании, пока это не будет вызвано? Поскольку нет нитей, я думаю, что это так...
4b9b3361

Ответ 1

Вы можете думать о await как о "приостановке" метода async до завершения этой операции. В качестве особого случая, если операция уже завершена (или очень быстро), то await не "приостанавливает" метод; он будет продолжать выполнение немедленно.

Итак, в этом случае (предполагая, что WriteStartDocumentAsync еще не завершено), await остановит метод и вернет незавершенную задачу вызывающему. Обратите внимание, что Task, возвращаемый методом async, представляет этот метод; когда метод завершается, тогда завершается Task.

В конце концов, WriteStartDocumentAsync завершится, и это закроет оставшуюся часть метода async для продолжения работы. В этом случае он выполнит следующую часть метода до следующего await, когда он снова будет приостановлен, и т.д. В конце концов завершится метод async, который завершит Task, который был возвращен представляют этот метод.

Для получения дополнительной информации у меня есть async/await intro в моем блоге.

Ответ 2

Ответ Стивенса, конечно, правильный. Вот еще один способ подумать об этом, который мог бы помочь.

Продолжение куска кода - это то, что происходит после завершения кода. Когда вы нажмете await, произойдет две вещи. Во-первых, текущая позиция в исполнении становится продолжением ожидаемой задачи. Во-вторых, управление оставляет текущий метод и некоторые другие прогоны кода. Другой код, возможно, является продолжением первого вызова или, может быть, является чем-то другим, обработчиком событий, например.

Но когда вызов xmlWriter.WriteStartDocumentAsync() завершен; что происходит? Прервано ли текущее исполнение и возвращается обратно в SaveAllAsync()?

Непонятно, что вы подразумеваете под вызовом "завершение". WriteStartDocumentAsync запускает асинхронную запись, возможно, в потоке завершения ввода-вывода и возвращает вам Task, который представляет эту асинхронную работу. Ожидание этой задачи делает две вещи, как я уже сказал. Во-первых, продолжение этой задачи становится текущей позицией кода. Во-вторых, управление оставляет текущий метод и некоторые другие прогоны кода. В этом случае любой код под названием SaveAllAsync выполняет продолжение этого вызова.

Теперь давайте предположим, что код - вызывающий элемент SaveAllAsync продолжает работать и позволяет предположить, что вы находитесь в приложении с потоком пользовательского интерфейса, например, в приложении Windows Forms или в приложении WPF. Теперь у нас есть два потока: поток пользовательского интерфейса и поток завершения ввода-вывода. В потоке пользовательского интерфейса работает вызывающий элемент SaveAllAsync, который в конечном итоге возвращается, и теперь поток пользовательского интерфейса просто сидит там в цикле, обрабатывая сообщения Windows для запуска обработчиков событий.

В конце концов IO завершает работу, и поток завершения ввода-вывода отправляет заметку в поток пользовательского интерфейса, в котором говорится: "Теперь вы можете продолжить выполнение этой задачи". Если поток пользовательского интерфейса занят, это сообщение ставится в очередь; в конечном итоге поток пользовательского интерфейса получает к нему и вызывает продолжение. Контроль возобновляется после первого await, и вы вводите цикл.

Теперь вызывается WriteStartElementAsync. Он снова запускает некоторый код, который зависит от чего-то, что происходит в потоке завершения ввода-вывода (предположительно, как это делает его работа, но это разумное предположение), который возвращает Task, представляющий эту работу, и пользовательский интерфейс поток ожидает эту задачу. Опять же, текущая позиция в исполнении регистрируется как продолжение этой задачи, и управление возвращает вызывающему, вызывающему первое продолжение, а именно процессор событий потока пользовательского интерфейса. Он продолжает весело обрабатывать сообщения до тех пор, пока один из потоков ввода-вывода не сообщит об этом, и говорит, что работа, которую вы просили, выполняется в потоке завершения ввода-вывода, пожалуйста, запустите продолжение этой задачи, и поэтому мы снова обходим цикл...

Имеют смысл?

Ответ 3

Всякий раз, когда функция является "асинхронной", это означает, что когда "ожидания" выполняются в System.Threading.Tasks.Task, происходят две основные вещи:

  • Текущая позиция в выполнении становится "продолжением" ожидаемой задачи, а это означает, что после завершения задачи она будет делать то, что необходимо, чтобы удостовериться, что остальная часть метода асинхронного вызова вызвана. Эта работа может быть выполнена в определенном потоке, в некотором потоке пула потоков или, возможно, в потоке пользовательского интерфейса, это зависит от SynchronizationContext, который Task получает за его "продолжение" .

  • Элемент управления возвращается либо:

    • Если это первый запрос в методе async, он возвращает исходный метод вызова с помощью System.Threading.Tasks.Task, который представляет метод async (НЕ любой из Заданий, созданных в методе async). Он может просто проигнорировать его и продолжить, дождаться его с помощью Task.Result/Task.Wait() (осторожно, чтобы вы не блокировали поток пользовательского интерфейса) или даже не ожидали, если он сам является асинхронным методом.

    • Если он не является первым ожиданием в методе async, он просто вернется к тому, какой обработчик в том, какой поток выполнял "продолжение" последней ожидаемой задачи.

Итак, чтобы ответить:

Когда они ждут SaveAllAsync() (или ждут задания)... Что происходит?

Выполнение ожидания SaveAllAsync() не будет ОБЯЗАТЕЛЬНО заставлять его застревать на любом из внутренних ожиданий. Это связано с тем, что ожидание на SaveAllAsync() просто возвращается к вызывающему абоненту любого метода под названием SaveAllAsync(), который может выполняться, как ничего не произошло, как и SaveAllAsync(), которое ожидает от него обратно. Это предотвращает перемещение потока и возможность ответить (потенциально) на запрос в более позднее время для запуска "продолжения" SaveAllAsync() с первого внутреннего ожидания: wait xmlWriter.WriteStartDocumentAsync(). Таким образом, бит-бит, SaveAllAsync() в конечном итоге закончится, и ничего не застрянет.

НО... если вы ИЛИ какой-либо другой код глубже никогда не выполняете Task.Result/Task.Wait() в любой из задач, возвращаемых ожиданием, это может заставить вещи застревать, если "продолжение" пытается запускается в том же потоке, что и код ожидания.