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

C# C 5.0中的异步:Eric Lippert的示例如何工作?,c#,asynchronous,c#-5.0,C#,Asynchronous,C# 5.0,我正在阅读Eric Lippert博客上关于C5新异步特性的文章。在那里,他使用了一个从远程位置获取文档的方法示例,一旦检索到文档,就将其归档到存储驱动器上。这是他使用的代码: async Task<long> ArchiveDocumentsAsync(List<Url> urls) { long count = 0; Task archive = null; for(int i = 0; i < urls.Count; ++i) { va

我正在阅读Eric Lippert博客上关于C5新异步特性的文章。在那里,他使用了一个从远程位置获取文档的方法示例,一旦检索到文档,就将其归档到存储驱动器上。这是他使用的代码:

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;
} 
补充问题:


如果停止执行,是否可以继续提取文档?如果是,如何存档?

这段代码使它在开始存档下一个文档之前等待上一个文档被存档。而且只有在第二个文件开始归档后,它才会开始下载第三个文件

if (archive != null)
      await archive;

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

这段代码使它在开始归档下一个文档之前,要等到上一个文档归档。而且只有在第二个文件开始归档后,它才会开始下载第三个文件

if (archive != null)
      await archive;

但我认为通常抓取速度很慢,因为它是从internet下载的,而归档速度很快,因为它是到本地硬盘的。当然,这取决于您的具体使用情况。

如果不使用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;
  }
请注意,我们从来没有两个回迁或两个拱在同一时间。第二次存档在第一次存档完成之前无法启动,第三次提取在第二次存档开始之前无法启动

*现在来看异步魔法:

编译器生成代码,这样等待的调用实际上不会阻止当前线程的执行。函数ArchiveDocumentsAsync只会让位于其调用者,除非调用者正在等待结果——在这种情况下,流会让位于调用者,依此类推。 编译器生成的机器确保在等待的任务完成后,执行停止后继续执行


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

如果不使用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;
  }
请注意,我们从来没有两个回迁或两个拱在同一时间。第二次存档在第一次存档完成之前无法启动,第三次提取在第二次存档开始之前无法启动

*现在来看异步魔法:

编译器生成代码,这样等待的调用实际上不会阻止当前线程的执行。函数ArchiveDocumentsAsync只会让位于其调用者,除非调用者正在等待结果——在这种情况下,流会让位于调用者,依此类推。 编译器生成的机器确保在等待的任务完成后,执行停止后继续执行

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

这段代码是开始提取第三个文档,还是等到第一个文档归档

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

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

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

我不想在本文中讨论任务组合器;我想专注于一个更简单的控制流

*如果不是下载文档并将其存档,而是获取本系列中的下一个视频并播放,那么您可能会关心它们发生的顺序。你不想把它们玩得乱七八糟 嗯,如果他们能更有效地到达无序。相反,您希望在当前播放时下载下一个

这段代码是开始提取第三个文档,还是等到第一个文档归档

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

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

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

我不想在本文中讨论任务组合器;我想专注于一个更简单的控制流


*如果不是下载文档并将其存档,而是获取本系列中的下一个视频并播放,那么您可能会关心它们发生的顺序。你不想让他们打乱秩序,即使他们能更有效地打乱秩序。相反,您希望在当前文件播放时下载下一个文件。

好吧,假设您从本地驱动器获取文档并将其上载到远程位置……好吧,假设您从本地驱动器获取文档并将其上载到远程位置。。。