【问题标题】:How to apply asynchronous inside Parallel.ForEach [duplicate]如何在 Parallel.ForEach 中应用异步 [重复]
【发布时间】:2025-12-01 22:15:01
【问题描述】:

Parallel.ForEach 方法内部,当我使用异步方法DownloadWebsiteAsync 获取外部资源时,调用方法executeAsync_Click 不会等待RunDownloadParallelAsync 方法并在RunDownloadParallelAsync 完成其工作之前打印结果。 当我将DownloadWebsiteAsync 更改为DownloadWebsiteSync(以同步方式工作)时,一切正常。

private async void executeAsync_Click(object sender, RoutedEventArgs e)
{
    var watch = System.Diagnostics.Stopwatch.StartNew();

    var results = await DemoMethods.RunDownloadParallelAsync();
    PrintResults(results);

    watch.Stop();
    var elapsedMs = watch.ElapsedMilliseconds;

    resultsWindow.Text += $"Total execution time: { elapsedMs }";
}

public static async Task<List<WebsiteDataModel>> RunDownloadParallelAsync()
{
    List<string> websites = PrepData();
    List<WebsiteDataModel> output = new List<WebsiteDataModel>();
    
    await Task.Run(() =>
        Parallel.ForEach<string>(websites, async (site) =>
        {
            WebsiteDataModel results =await DownloadWebsiteAsync(site);
            output.Add(results);
        })
    );
    return output;
}

【问题讨论】:

  • 我认为这里不需要并行性。您可以简单地遍历所有网站,调用DownloadWebsiteAsync,将生成的Task&lt;WebsiteDataModel&gt; 放在一个列表中(比如downloadTasks),然后在所有网站上调用DownloadWebsiteAsync 之后(循环之后)调用await Task.WhenAll(downloadTasks),然后得到类似here 描述的结果
  • 如果您的方法是async,则不需要Parallel.ForEach,这不会增加任何内容。您现在正在做的是启动一个单独的线程,该线程启动启动任务的线程,更糟糕的是,这仍然无法正常工作,因为output.Add 不是线程安全的!请考虑使用Task.WhenAll
  • 如果您希望限制可以一起运行的请求的数量,您可以执行以下操作:devblogs.microsoft.com/pfxteam/…。如果您只有少量任务,最好使用Task.WhenAll
  • Parallel.ForEach 不是异步友好的。 @Servy this 是正确的重复恕我直言。
  • @TheodorZoulias 您刚刚将另一个问题与相同的解决方案联系起来。当然这是一个常见问题,因此搜索标题会导致大量重复。

标签: c# .net async-await


【解决方案1】:

Parallelasync 通常不能很好地协同工作。

在这里您希望允许多个 HTTP 请求同时发生,但在作业中抛出多个线程将无济于事。

相反,只需使用async:

public static async Task<IEnumerable<WebsiteDataModel>> RunDownloadParallelAsync()
{
    return await Task.WhenAll(PrepData().Select(DownloadWebsiteAsync));
}

每个请求都将被发送,无需等待前一个响应。

【讨论】:

    【解决方案2】:

    如果您需要限制同时“工作人员”的数量,请使用TPL DataFlow 中的ActionBlock

    【讨论】: