C# Parallel.ForEach未按预期在C中的ConcurrentBag中添加项目
在我的Asp.Net核心WebApi控制器中,我接收到一个文件[]文件。我需要将此转换为列表的列表。我第一次用foreach。它工作得很好。但后来我决定改为Parallel.ForEach,因为我收到了许多>5个文件 这是我的DocumentData类: 公共类文档数据 { 公共字节[]二进制数据{get;set;} 公共字符串文件名{get;set;} } 以下是我的Parallel.ForEach逻辑: var文件=新的ConcurrentBag; Parallel.ForEachfiles,异步currentFile=> { 如果currentFile.Length>0 { 使用var ms=新内存流 { 等待currentFile.CopyToAsynchms; 文档。添加新的文档数据 { BinaryData=ms.ToArray, FileName=currentFile.FileName }; } } }; 例如,即使两个文件作为输入,文档也总是给出一个文件作为输出。我错过什么了吗 我最初有一份清单。我发现它不是线程安全的,于是改为ConcurrentBag。但我还是得到了意想不到的结果。请帮助说明我错在哪里?我想这是因为Parallel.Foreach不支持async/await。它只将操作作为输入并对每个项目执行。在异步委托的情况下,它将以一种先发后忘的方式执行它们。 在这种情况下,传递的lambda将被视为异步void函数,不能等待异步void 如果有需要Func的重载,那么它就会工作 我建议您在选择和使用Task.whell的帮助下创建任务,以便同时执行它们 例如:C# Parallel.ForEach未按预期在C中的ConcurrentBag中添加项目,c#,multithreading,concurrency,asp.net-core-webapi,parallel.foreach,C#,Multithreading,Concurrency,Asp.net Core Webapi,Parallel.foreach,在我的Asp.Net核心WebApi控制器中,我接收到一个文件[]文件。我需要将此转换为列表的列表。我第一次用foreach。它工作得很好。但后来我决定改为Parallel.ForEach,因为我收到了许多>5个文件 这是我的DocumentData类: 公共类文档数据 { 公共字节[]二进制数据{get;set;} 公共字符串文件名{get;set;} } 以下是我的Parallel.ForEach逻辑: var文件=新的ConcurrentBag; Parallel.ForEachfiles
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您对并发集合的想法是正确的,但误用了TPL方法 简而言之,您需要非常小心异步lambda,如果要将它们传递给操作或Func 您的问题是因为Parallel.For/ForEach不适合异步和等待模式或IO绑定任务。它们适合cpu限制的工作负载。这意味着它们基本上有动作参数,让任务调度器为您创建任务 如果您想同时运行多个任务,请使用Task.WhenAll或TPL Dataflow ActionBlock,它可以有效地处理CPU绑定和IO绑定的工作负载,或者更直接地说,它们可以处理异步方法所具有的任务 最基本的问题是,当您对一个操作调用异步lambda时,实际上是在创建一个异步void方法,它将作为一个未被观察到的任务运行。也就是说,您的TPL方法只是并行创建一组任务来运行一组未观察到的任务,而不是等待它们
想象一下,你让一群朋友去给你买些食品,他们反过来告诉别人给你买食品,但是你的朋友向你汇报,说他们的工作完成了。显然不是,而且你没有食品杂货 如果您正在使用Task.WhenAll,那么您需要等待它。@FarhadJabiyev,现在不需要ConcurrentBag了,对吗?相反,我可以使用List@fingers10当然,因为Parallel.ForEach不支持异步lambdas。您可以使用simple foreach按顺序执行它们,也可以使用Task.Whall同时执行它们作为奖励,您可以从Select lambda返回DocumentData,然后等待Task.Whalll。。将产生DocumentData[]。@StephenCleary这主意不错。值得用这个想法更新答案。行。
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();