C#5.0中的异步:Eric Lippert的示例如何工作?

时间:2011-02-27 11:16:29

标签: c# asynchronous c#-5.0

我正在阅读Eric Lippert博客上有关C#5新异步功能的精彩article series。在那里,他使用从远程位置获取文档的方法示例,并且一旦检索到,就将它们存档在存储驱动器上。这是他使用的代码:

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;
}

现在想象一下提取文档非常快。所以提取了第一个文档。之后,它开始存档,而第二个文档正在被提取。现在想象一下,第二个文档已被提取,第一个文档仍在存档。这段代码是否会开始获取第三个文档或等到第一个文档被存档?

正如Eric在其文章中所说,编译器将此代码转换为:

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 个答案:

答案 0 :(得分:9)

  

这段代码是否会开始提取第三个文档或等到第一个文档被存档?

等待。本文的目的是描述控制流如何与转换一起工作,而不是实际描述管理fetch-archive操作的最佳系统。

假设你确实有一百个文档需要获取和存档,你真的不在乎它们发生了什么顺序。(*)你可以创建一个新的异步方法“FetchAndArchive”,它异步获取一个文档然后归档它异步。然后,您可以从另一个异步方法中调用该方法一百次,该异步方法生成一百个任务,每个任务异步获取一个文档并将其归档。 方法的结果是一个组合任务,表示完成这100个任务的工作,每个任务代表执行两个任务的工作。

在这种情况下,只要其中一个获取操作无法立即生成其结果,就可以运行其中一个准备执行其存档步骤的任务。

我不想在本文中涉及任务组合器;我想专注于更简单的控制流程。


(*)你可能会关心他们发生了什么顺序,而不是“下载文档并将其存档”,操作是“获取本系列中的下一个视频并播放它”。您不希望无序播放它们,即使它们可以更有效地到达无序状态。相反,你想在当前播放的时候下载下一个。

答案 1 :(得分:2)

这段代码使它等到上一个文档被存档,然后开始存档下一个文档。它只会在开始存档第二个时开始下载第三个。

if (archive != null)
      await archive;

但我认为通常提取速度很慢,因为它是从互联网下载的,而归档是快速的,因为它是本地硬盘。但当然这取决于您的确切用例。

答案 2 :(得分:0)

不使用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;
  }

请注意,我们从不同时拥有两个Fetches或两个Archivings。第一个存档完成之前无法启动第二个存档,并且在第二个存档启动之前无法启动第三个存档。

(*)现在为异步魔法:

编译器生成代码,以便对Wait()的调用实际上不会阻止当前线程的执行。函数ArchiveDocumentsAsync简单地“产生”给它的调用者(除非调用者的结果是await - 在这种情况下,流量被调用给调用者调用者,依此类推)。 /> 编译器生成的机器确保在Wait ed任务完成后,如果它已经停止,则会立即继续执行。

注意: Eric Lippert已经回答了这个问题。我只想给我两分钱并写下我的理解,这样你们就可以在这里警告它是否错了。