簡體   English   中英

Parallel.ForEach 和 async-await

[英]Parallel.ForEach and async-await

我有這樣的方法:

public async Task<MyResult> GetResult()
{
    MyResult result = new MyResult();

    foreach(var method in Methods)
    {
        string json = await Process(method);

        result.Prop1 = PopulateProp1(json);
        result.Prop2 = PopulateProp2(json);

    }

    return result;
}

然后我決定使用Parallel.ForEach

public async Task<MyResult> GetResult()
{
    MyResult result = new MyResult();

    Parallel.ForEach(Methods, async method =>
    {
        string json = await Process(method);    

        result.Prop1 = PopulateProp1(json);
        result.Prop2 = PopulateProp2(json);
    });

    return result;
}

但現在我有一個錯誤:

異步模塊或處理程序已完成,而異步操作仍處於掛起狀態。

async不適用於ForEach 特別是,您的async lambda 正在轉換為async void方法。 避免async void原因有很多(正如我在 MSDN 文章中所描述的); 其中之一是您無法輕松檢測async lambda 何時完成。 ASP.NET 將看到您的代碼在沒有完成async void方法的情況下返回並(適當地)拋出異常。

您可能想要做的是並發處理數據,而不是並行處理 ASP.NET 上幾乎不應該使用並行代碼。 以下是異步並發處理的代碼:

public async Task<MyResult> GetResult()
{
  MyResult result = new MyResult();

  var tasks = Methods.Select(method => ProcessAsync(method)).ToArray();
  string[] json = await Task.WhenAll(tasks);

  result.Prop1 = PopulateProp1(json[0]);
  ...

  return result;
}

或者,使用AsyncEnumerator NuGet 包,您可以執行以下操作:

using System.Collections.Async;

public async Task<MyResult> GetResult()
{
    MyResult result = new MyResult();

    await Methods.ParallelForEachAsync(async method =>
    {
        string json = await Process(method);    

        result.Prop1 = PopulateProp1(json);
        result.Prop2 = PopulateProp2(json);
    }, maxDegreeOfParallelism: 10);

    return result;
}

其中ParallelForEachAsync是一個擴展方法。

啊,好吧。 我想我知道現在發生了什么。 async method =>一個“異步無效”,它是“即發即棄”(不推薦用於事件處理程序以外的任何東西)。 這意味着調用者無法知道它何時完成......因此, GetResult在操作仍在運行時返回。 盡管我的第一個答案的技術細節不正確,但這里的結果是相同的:GetResult 正在返回,而ForEach啟動的操作仍在運行。 您真正可以做的唯一一件事就是不要在Process await (這樣 lambda 不再是async )並等待Process完成每次迭代。 但是,這將使用至少一個線程池線程來執行此操作,從而略微強調池——可能使ForEach使用毫無意義。 我根本不會使用 Parallel.ForEach ...

.NET 6 最終添加了 Parallel.ForEachAsync,這是一種調度異步工作的方法,可讓您控制並行度:

var urlsToDownload = new [] 
{
    "https://dotnet.microsoft.com",
    "https://www.microsoft.com",
    "https://twitter.com/shahabfar"
};

var client = new HttpClient();

await Parallel.ForEachAsync(urlsToDownload, async (url, token) =>
{
    var targetPath = Path.Combine(Path.GetTempPath(), "http_cache", url);

    var response = await client.GetAsync(url);

    if (response.IsSuccessStatusCode)
    {
        using var target = File.OpenWrite(targetPath);

        await response.Content.CopyToAsync(target);
    }
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM