首页 > 解决方案 > Parallel.ForEach 未按预期在 C# 的 ConcurrentBag 中添加项目

问题描述

在我的Asp.Net Core WebApi Controller中,我收到一个IFormFile[] files. 我需要将其转换为 of List<DocumentData>。我第一次使用foreach. 它工作正常。但后来决定更改为Parallel.ForEach,因为我收到许多(> 5)个文件。

这是我的DocumentData课:

public class DocumentData
{
    public byte[] BinaryData { get; set; }
    public string FileName { get; set; }
}

这是我的Parallel.ForEach逻辑:

var documents = new ConcurrentBag<DocumentData>();
Parallel.ForEach(files, async (currentFile) =>
{
    if (currentFile.Length > 0)
    {
        using (var ms = new MemoryStream())
        {
            await currentFile.CopyToAsync(ms);
            documents.Add(new DocumentData
            {
                BinaryData = ms.ToArray(),
                FileName = currentFile.FileName
            });
        }
    }
});

例如,即使将两个文件作为输入,也documents总是将一个文件作为输出。我错过了什么吗?

我最初有List<DocumentData>. 我发现它不是线程安全的并更改为ConcurrentBag<DocumentData>. 但我仍然得到了意想不到的结果。请帮忙看看我哪里错了?

标签: c#multithreadingconcurrencyasp.net-core-webapiparallel.foreach

解决方案


我猜是因为,Parallel.Foreach不支持async/await。它只接受Action输入并为每个项目执行它。在异步委托的情况下,它将以一种即发即弃的方式执行它们。在这种情况下,传递的 lambda 将被视为async void函数并且async void不能等待。

如果有过载,Func<Task>那么它会起作用。

我建议您在创建Tasks 的帮助下Select同时使用Task.WhenAll它们来执行它们。

例如:

var tasks = files.Select(async currentFile =>
{
    if (currentFile.Length > 0)
    {
        using (var ms = new MemoryStream())
        {
            await currentFile.CopyToAsync(ms);
            documents.Add(new DocumentData
            {
                BinaryData = ms.ToArray(),
                FileName = currentFile.FileName
            });
        }
    }
});

await Task.WhenAll(tasks);

此外,您可以仅通过从该方法返回DocumentData实例来改进该代码,在这种情况下,无需修改documents集合。 Task.WhenAll具有IEnumerable<Task<TResult>作为输入并产生数组Task的重载。TResult所以,结果会是这样:

var tasks = files.Select(async currentFile =>
    {
        if (currentFile.Length > 0)
        {
            using (var ms = new MemoryStream())
            {
                await currentFile.CopyToAsync(ms);
                return new DocumentData
                {
                    BinaryData = ms.ToArray(),
                    FileName = currentFile.FileName
                };
            }
        }

        return null;
    });

var documents =  (await Task.WhenAll(tasks)).Where(d => d != null).ToArray();

推荐阅读