如何让异步代码等待所有任务完成?

How do I get async code to wait until all tasks are complete?

当我 运行 下面的代码时,消息 "Press Enter to continue... "HttpClient.GetAsync() 调用完成返回结果之前出现。事件的实际顺序:进行 GetAsync() 调用,出现“Press Enter...”消息,然后将结果一一输出到控制台 window。如何在显示“Press Enter...”消息之前等到所有 GetAsync() 调用完成?

class Program
{
  static HttpClient client = new HttpClient();

  static void Main(string[] args)
  {
    RunAsync().Wait();

    Console.WriteLine("\n\n\n\nPress Enter to continue... ");
    Console.ReadLine();
  }

  static async Task RunAsync()
  {
    List<string> urls = new List<string>()
    {
      "http://www.domain1.com",
      "http://www.domain2.com",
      "http://www.domain3.com",
      "http://www.domain4.com"
    };

    foreach (var url in urls)
    {
      DownloadPageAsync(url);
    }
  }

  static async Task<string> DownloadPageAsync(string url)
  {
    Console.WriteLine("Starting: " + url);

    HttpResponseMessage response = await client.GetAsync(url);

    if (response.IsSuccessStatusCode)
    {
      // do stuff here
    }

    Console.WriteLine("Done: " + url);

    return response.Content.ToString();
  }
}

由于DownloadPageAsyncreturns一个任务,你可以列出所有任务并等待它们:

Task.WhenAll(urls.Select(url => DownloadPageAsync(url)))

或简化:

Task.WhenAll(urls.Select(DownloadPageAsync))

我认为问题在于您没有在 RunAsync() 方法中等待 DownloadPageAsync 方法。如果您将 RunAsync() 方法更新为以下代码,那么我相信它会按您预期的那样工作:

static async Task RunAsync()
{
    List<string> urls = new List<string>()
    {
      "http://www.domain1.com",
      "http://www.domain2.com",
      "http://www.domain3.com",
      "http://www.domain4.com"
    };

    foreach (var url in urls)
    {
        // added await here
        await DownloadPageAsync(url);
    }
}

您需要为每次调用创建不同的任务,在您的示例中,您是 运行 代码而不是等待调用。当您调用 WhenAll 时,它只是为所有这些创建一个任务。假设您使用下面的代码,并在每个 MakeYouCall 方法中向列表中插入一个项目。该列表将是您需要锁定的共享资源。完成 WhenAll 后,如果您不等待结果(调用 Wait() ),则可能会部分填充集合。

var register1 = new Action(() => MakeYourCall1());
var register2 = new Action(() => MakeYourCall2());
var register3 = new Action(() => MakeYourCall3());

然后

var t1 = Task.Factory.StartNew(register1);
var t2 = Task.Factory.StartNew(register2);
var t3 = Task.Factory.StartNew(register3);

之后您可以调用将 return 任务的 WhenAll,然后等待它。

Task.WhenAll(t1, t2, t3).Wait();

@patrik-hofman 的回答很好(投了赞成票),不过请参阅我的评论

如果您希望请求按顺序发生... 将 await 添加到 DownloadPageAsync 行。

您在 RunAsync 中使用了 async,但没有 await。因此,尽管它 returns 是一个任务,但它并不等待 DownloadPageAsync 调用完成。这意味着该方法只是 returns 一个立即完成的 "empty" 任务。所以你的 .Wait() 什么都没等。