Асинхронность в C # 5.0: как работает пример Эрика Липперта? - PullRequest
1 голос
/ 27 февраля 2011

Я читаю замечательную серию статей в блоге Эрика Липперта о новых функциях асинхронности в C # 5.Там он использует пример метода извлечения документов из удаленного местоположения и после извлечения архивирует их на накопитель.Вот код, который он использует:

async Task<long> ArchiveDocumentsAsync(List<Url> urls)
{
  long count = 0;
  Task archive = null;
  for(int i = 0; i < urls.Count; ++i)
  {
    var document = await FetchAsync(urls[i]);
    count += document.Length;
    if (archive != null)
      await archive;
    archive = ArchiveAsync(document);
  }
  return count;
}

Теперь представьте, что выборка документов происходит очень быстро.Итак, первый документ получен.После этого он начинает архивироваться, пока идет выборка второго документа.Теперь представьте, что второй документ получен, а первый документ все еще архивируется.Будет ли этот фрагмент кода начинать извлечение третьего документа или ждать, пока первый документ будет заархивирован?

Как говорит Эрик в своей статье, этот код преобразуется компилятором в следующий код:

Task<long> ArchiveDocuments(List<Url> urls)
{
  var taskBuilder = AsyncMethodBuilder<long>.Create();
  State state = State.Start;
  TaskAwaiter<Document> fetchAwaiter = null;
  TaskAwaiter archiveAwaiter = null;
  int i;
  long count = 0;
  Task archive = null;
  Document document;
  Action archiveDocuments = () =>
  {
    switch(state)
    {
      case State.Start:        goto Start;
      case State.AfterFetch:   goto AfterFetch;
      case State.AfterArchive: goto AfterArchive;
    }
    Start:
    for(i = 0; i < urls.Count; ++i)
    {
      fetchAwaiter = FetchAsync(urls[i]).GetAwaiter();
      state = State.AfterFetch;
      if (fetchAwaiter.BeginAwait(archiveDocuments))
        return;
      AfterFetch:
      document = fetchAwaiter.EndAwait();
      count += document.Length;
      if (archive != null)
      {
        archiveAwaiter = archive.GetAwaiter();
        state = State.AfterArchive;
        //----> interesting part! <-----
        if (archiveAwaiter.BeginAwait(archiveDocuments))
          return; //Returns if archive is still working => Fetching of next document not done
        AfterArchive:
        archiveAwaiter.EndAwait();
      }
      archive = ArchiveAsync(document);
    }
    taskBuilder.SetResult(count);
    return;
  };
  archiveDocuments();
  return taskBuilder.Task;
} 

Дополнительный вопрос:

Если выполнение будет остановлено, можно ли продолжить получение документов?Если да, то как?

Ответы [ 3 ]

9 голосов
/ 27 февраля 2011

Будет ли этот фрагмент кода начинать извлечение третьего документа или ждать, пока первый документ будет заархивирован?

Это ждет. Смысл статьи в том, чтобы описать, как поток управления работает с преобразованием, а не в действительности описать наилучшую возможную систему для управления операцией выборки-архивирования.

Предположим, у вас есть сотни документов для извлечения и архивирования, и вам действительно все равно, в каком порядке они произошли. (*) Вы можете создать новый асинхронный метод "FetchAndArchive", который извлекает один документ асинхронно, а затем архивирует его. асинхронно. Затем вы могли бы вызывать этот метод сто раз из другого асинхронного метода, который выполняет сто задач, каждая из которых асинхронно извлекает документ и архивирует его. Результатом того метода является комбинированная задача, представляющая работу по выполнению этих сотен задач, каждая из которых представляет работу по выполнению двух задач.

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

Я не хотел вдаваться в комбинаторы задач в этой статье; Я хотел сосредоточиться на более простом потоке управления.


(*) Вас может волновать, в каком порядке они произошли, если вместо «загрузить документ и заархивировать его» операция была «получить следующее видео в этой серии и воспроизвести его». Вы не хотите воспроизводить их не по порядку, даже если они могут более эффективно поступать не по порядку. Скорее, вы хотите загрузить следующий, пока проигрывается текущий.

2 голосов
/ 27 февраля 2011

Этот фрагмент кода заставляет его ждать, пока предыдущий документ будет заархивирован, прежде чем начинать архивировать следующий. И он начнет загружать третий только после того, как начнет архивировать второй.

if (archive != null)
      await archive;

Но я думаю, что обычно загрузка происходит медленно, потому что она загружается из Интернета, тогда как архивирование происходит быстро, поскольку оно осуществляется на локальный жесткий диск. Но, конечно, это зависит от вашего конкретного случая использования.

0 голосов
/ 27 февраля 2011

Без использования async / await та же * функция в псевдокоде будет выглядеть примерно так:

  long ArchiveDocumentsAsync(List<Url> urls)
  {
    long count = 0;
    Task archive = null;

    for(int i = 0; i < urls.Count; ++i)
    {
      Task<Something> documentTask = FetchAsync(urls[i]);

      //Wait for the completion of the task.
      documentTask.Wait();

      //Get the results. 
      Something document = documentTask.getReturnValue();

      count += document.Length;

      if (archive != null) {
        //Wait for the completion of the task.
        archive.Wait(); 
      }

      archive = ArchiveAsync(document);
    }
    return count;
  }

Обратите внимание, что у нас никогда не бывает двух выборок или двух архивов одновременно. 2-я архивация не может начаться до того, как 1-я архивация будет завершена, а 3-я выборка не может начаться до того, как будет начата 2-я архивация.

(*) Теперь для асинхронной магии:

Компилятор генерирует код, чтобы вызовы Wait() фактически не блокировали выполнение текущего потока. Функция ArchiveDocumentsAsync просто «уступает» своему вызывающему (за исключением случаев, когда ее вызывающий await запрашивает свои результаты - в этом случае поток передается вызывающему вызывающему и т. Д.).
Механизм, сгенерированный компилятором, гарантирует, что выполнение будет продолжено сразу же после его остановки после выполнения задачи Wait.

Примечание: Эрик Липперт уже ответил на этот вопрос. Я просто хочу дать свои два цента и записать мое понимание, чтобы вы, ребята, могли предупредить здесь, если это неправильно.

...