fin*_*s10 2 c# concurrency multithreading parallel.foreach asp.net-core-webapi
在我的 中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; }
}
Run Code Online (Sandbox Code Playgroud)
这是我的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
});
}
}
});
Run Code Online (Sandbox Code Playgroud)
例如,即使有两个文件作为输入,也documents总是给出一个文件作为输出。我错过了什么吗?
我最初有List<DocumentData>. 我发现它不是线程安全的并更改为ConcurrentBag<DocumentData>. 但我仍然得到了意想不到的结果。请帮助我错在哪里?
我猜是因为,Parallel.Foreach不支持async/await. 它仅Action作为输入并为每个项目执行它。在异步委托的情况下,它将以即发即忘的方式执行它们。在这种情况下,传递的 lambda 将被视为async void函数,并且async void不能等待。
如果有超载需要Func<Task>那么它会工作。
我建议您在Tasks 的帮助下创建sSelect并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);
Run Code Online (Sandbox Code Playgroud)
此外,您只需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();
Run Code Online (Sandbox Code Playgroud)