【问题标题】:Asynchrony in C# 5.0: How does Eric Lippert's example work?C# 5.0 中的异步:Eric Lippert 的示例如何工作?
【发布时间】:2011-07-05 05:01:00
【问题描述】:

我正在阅读 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;
} 

补充问题:

如果停止执行,是否可以继续获取文档?如果是,怎么做?

【问题讨论】:

    标签: c# asynchronous c#-5.0


    【解决方案1】:

    这段代码让它等到前一个文档被归档后再开始归档下一个文档。并且只有在开始存档第二个时才会开始下载第三个。

    if (archive != null)
          await archive;
    

    但我认为通常获取速度很慢,因为它是从 Internet 下载的,而归档速度很快,因为它是在本地硬盘上。但这当然取决于您的具体用例。

    【讨论】:

    • 好吧,假设您从本地驱动器获取文档并将它们上传到远程位置...
    【解决方案2】:

    如果不使用 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 或两个 Archiving。 2nd Archiving 不能在 1st Archiving 完成之前开始, 3rd Fetch 不能在 2nd Archiving 开始之前开始。

    (*) 现在是异步魔法:

    编译器生成代码以便对Wait() 的调用实际上不会阻塞当前线程的执行。函数ArchiveDocumentsAsync 只是“让步”给它的调用者(除非它的调用者是await 以获取其结果——在这种情况下,流程将让给调用者-调用者,依此类推)。
    Waited 任务完成后,编译器生成的机器会确保在它被停止的情况下继续执行。

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

    【讨论】:

      【解决方案3】:

      这段代码是开始获取第三个文档还是等到第一个文档被存档?

      等待。本文的重点是描述控制流如何与转换一起工作,而不是实际描述管理提取-归档操作的最佳系统。

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

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

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


      (*) 如果操作不是“下载文档并存档”,而是“获取本系列中的下一个视频并播放”,您可能会关心它们发生的顺序。您不想乱序播放它们,即使它们可以更有效地乱序到达。相反,您想在当前播放时下载下一个。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-05
        • 1970-01-01
        • 1970-01-01
        • 2011-05-02
        • 2012-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多