c# - 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>
. 但我仍然得到了意想不到的结果。请帮忙看看我哪里错了?
解决方案
我猜是因为,Parallel.Foreach
不支持async/await
。它只接受Action
输入并为每个项目执行它。在异步委托的情况下,它将以一种即发即弃的方式执行它们。在这种情况下,传递的 lambda 将被视为async void
函数并且async void
不能等待。
如果有过载,Func<Task>
那么它会起作用。
我建议您在创建Task
s 的帮助下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();