Parallel.ForEach阻止调用方法

时间:2020-05-18 13:27:31

标签: c# parallel-processing async-await parallel.foreach

我对Parallel.ForEach有疑问。我编写了一个简单的应用程序,将要下载的文件名添加到队列中,然后使用while循环遍历队列,一次下载一个文件,然后在下载文件后,调用另一种异步方法从下载的文件中创建对象memoryStream。不等待此方法的返回结果,它被丢弃,因此下一次下载将立即开始。如果我在对象创建过程中使用简单的foreach,一切都会正常进行-在​​继续下载的同时创建对象。但是,如果我想加快对象创建过程并使用Parallel.ForEach,它将停止下载过程,直​​到创建对象为止。用户界面完全响应,但不会下载下一个对象。我不明白为什么会这样-Parallel.ForEach位于await 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));
        //});
    });
}

编辑
这是简化的对象创建方法:

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;
}

1 个答案:

答案 0 :(得分:2)

不等待此方法的返回结果,它被丢弃,因此下一次下载将立即开始。

这也是危险的。 “解雇后忘记”的意思是“我不在乎此操作何时完成或是否完成。只需丢弃所有异常,因为我不在乎。”因此,在实践中,抛弃式的应该极为罕见。在这里不合适。

UI可以完全响应,但不会下载下一个对象。

我不知道为什么它会阻止下载,但是切换到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;
}
相关问题