簡體   English   中英

Parallel.ForEach 阻塞調用方法

[英]Parallel.ForEach blocking calling method

我遇到了Parallel.ForEach的問題。 我編寫了簡單的應用程序,將要下載的文件名添加到隊列中,然后使用 while 循環遍歷隊列,一次下載一個文件,然后在下載文件后,調用另一個異步方法從下載的文件中創建 object memoryStream 該方法的返回結果不等待,直接丟棄,立即開始下一次下載。 如果我在 object 創建中使用簡單的foreach ,一切正常 - 在繼續下載的同時創建對象。 但是,如果我想加快 object 創建過程並使用Parallel.ForEach它會停止下載過程,直到創建 object。 UI 完全響應,但它不會下載下一個 object。 我不明白為什么會發生這種情況 - Parallel.ForEachawait Task.Run()內部,並且據我對異步編程的有限了解,這應該可以解決問題。 誰能幫我理解為什么它會阻止第一種方法以及如何避免它?

這是一個小樣本:

public async Task DownloadFromCloud(List<string> constructNames)
{
    _downloadDataQueue = new Queue<string>();
    var _gcsClient = StorageClient.Create();

    foreach (var item in constructNames)
    {
        _downloadDataQueue.Enqueue(item);
    }

    while (_downloadDataQueue.Count > 0)
    {
        var memoryStream = new MemoryStream();
        await _gcsClient.DownloadObjectAsync("companyprojects",
            _downloadDataQueue.Peek(), memoryStream);
        memoryStream.Position = 0;
        _ = ReadFileXml(memoryStream);
        _downloadDataQueue.Dequeue();
    }
}

private async Task ReadFileXml(MemoryStream memoryStream)
{
    var reader = new XmlReader();
    var properties = reader.ReadXmlTest(memoryStream);
    await Task.Run(() =>
    {
        var entityList = new List<Entity>();
        foreach (var item in properties)
        {
            entityList.Add(CreateObjectsFromDownloadedProperties(item));
        }
        //Parallel.ForEach(properties item =>
        //{
        //    entityList.Add(CreateObjectsFromDownloadedProperties(item));
        //});
    });
}

編輯
這是簡化的object創建方法:

public Entity CreateObjectsFromDownloadedProperties(RebarProperties properties)
{
    var path = new LinearPath(properties.Path);
    var section = new Region(properties.Region);
    var sweep = section.SweepAsMesh(path, 1);
    return sweep;
}

該方法的返回結果不等待,直接丟棄,立即開始下一次下載。

這也是危險的。 “一勞永逸”的意思是“我不在乎此操作何時完成,或者它是否完成。只需丟棄所有異常,因為我不在乎。” 因此,一勞永逸在實踐中應該是極其罕見的。 這里不合適。

UI 完全響應,但它不會下載下一個 object。

我不知道為什么它會阻止下載,但是切換到Parallel.ForEach存在一個明確的問題: List<T>.Add不是線程安全的。

private async Task ReadFileXml(MemoryStream memoryStream)
{
    var reader = new XmlReader();
    var properties = reader.ReadXmlTest(memoryStream);
    await Task.Run(() =>
    {
        var entityList = new List<Entity>();
        Parallel.ForEach(properties, item =>
        {
            var itemToAdd = CreateObjectsFromDownloadedProperties(item);
            lock (entityList) { entityList.Add(itemToAdd); }
        });
    });
}

一個提示:如果你有一個結果值, PLINQ通常比Parallel更干凈:

private async Task ReadFileXml(MemoryStream memoryStream)
{
    var reader = new XmlReader();
    var properties = reader.ReadXmlTest(memoryStream);
    await Task.Run(() =>
    {
        var entityList = proeprties
            .AsParallel()
            .Select(CreateObjectsFromDownloadedProperties)
            .ToList();
    });
}

然而,代碼仍然存在即發即棄的問題。

為了更好地解決問題,我建議退后一步,使用更適合“管道”式處理的東西。 例如,TPL 數據流:

public async Task DownloadFromCloud(List<string> constructNames)
{
  // Set up the pipeline.
  var gcsClient = StorageClient.Create();
  var downloadBlock = new TransformBlock<string, MemoryStream>(async constructName =>
  {
    var memoryStream = new MemoryStream();
    await gcsClient.DownloadObjectAsync("companyprojects", constructName, memoryStream);
    memoryStream.Position = 0;
    return memoryStream;
  });
  var processBlock = new TransformBlock<MemoryStream, List<Entity>>(memoryStream =>
  {
    var reader = new XmlReader();
    var properties = reader.ReadXmlTest(memoryStream);
    return proeprties
        .AsParallel()
        .Select(CreateObjectsFromDownloadedProperties)
        .ToList();
  });
  var resultsBlock = new ActionBlock<List<Entity>>(entities => { /* TODO */ });
  downloadBlock.LinkTo(processBlock, new DataflowLinkOptions { PropagateCompletion = true });
  processBlock.LinkTo(resultsBlock, new DataflowLinkOptions { PropagateCompletion = true });

  // Push data into the pipeline.
  foreach (var constructName in constructNames)
    await downloadBlock.SendAsync(constructName);
  downlodBlock.Complete();

  // Wait for pipeline to complete.
  await resultsBlock.Completion;
}

暫無
暫無

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

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