在我的Asp.Net Core WebApi Controller ,我收到了IFormFile[] files 我需要将其转换为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> 但我仍然得到了意想不到的结果。 请帮助我错在哪里?

#1楼 票数:6 已采纳

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

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

我建议您在Select的帮助下创建Task并使用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>作为输入并生成TResult数组的Task 所以,结果会是这样:

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();

#2楼 票数:3

您对并发集合有正确的想法,但误用了TPL 方法

简而言之,您需要非常小心异步 lambdas ,如果您将它们传递给ActionFunc<Task>

您的问题是因为Parallel.For / ForEach不适合async 和 await 模式IO 绑定任务 它们适用于受CPU 限制的工作负载 这意味着它们本质上具有Action参数,让任务调度程序为您创建任务

如果您想同时运行多个任务,请使用Task.WhenAll ,或者可以有效处理CPU 绑定IO 绑定的工作负载的TPL Dataflow ActionBlock ,或者更直接地说,它们可以处理异步任务方法是。

根本问题是,当您在Action上调用异步 lambda时,您实际上是在创建一个async void方法,该方法将作为未观察到的任务运行。 也就是说,你的TPL 方法只是并行创建一堆任务来运行一堆未观察到的任务,而不是等待它们。

可以这样想,你让一群朋友去给你买一些杂货,他们又告诉别人去买你的杂货,但你的朋友向你汇报并说他们的工作完成了。 显然不是,而且你没有杂货。

  ask by fingers10 translate from so

未解决问题?本站智能推荐:

3回复

如何执行“ Parallel.ForEach”作为后台任务,将控制立即返回到调用方法?

如何执行“ Parallel.ForEach”作为后台任务,该任务将立即将控制权返回给调用方法? 我想使用C#/ Linq“ Parallel.Foreach”,但我不想一直等到所有并行任务都完成后才能继续执行call方法中的下一条语句。 我正在寻找一种从另一个线程异步处理“ Paral
2回复

Parallel.ForEach 循环与 C# 中的重试逻辑

我正在使用Parallel.ForEach将 C# 中的多个文件从谷歌存储桶下载到文件夹位置。 我正在使用重试逻辑,因此它可以重试下载文件,以防在下载过程中文件下载失败。 如何为Parallel.ForEach循环中的每个文件或每个线程应用重试逻辑。
1回复

为Parallel.ForEach中的项目设置超时

我正在使用Parallel.ForEach处理并发词典集合。 ConcurrentDictionary包含“密钥”和“字节”字段。 concurrentDictionary-是此处的并发字典集合。 我想设置超时时间,如果任何特定项目花费的最大时间设置为60秒。 如果耗时超过60
2回复

在C#中的Parallel.ForEach中读/写字典条目是否安全

我有一个字典,我想在foreach循环中修改它的值,但是,由于我的应用程序对时间要求很高,因此我试图避免所有不必要的锁定开销。 标记的命令安全吗? 也就是说,无需锁定即可读/写字典的不同键值对。 注意1:我知道并发集合名称空间及其所有宏伟的集合。 我也知道我可以简单地lock突
1回复

Parallel.foreach不会处理所有项目

我在这里有问题。 我试图使用Parallel.foreach将我的数据表转换为列表对象。 像这样 。 我有超过65000个产品行。 之后。 列表中仅添加了约63000个产品。 但是结果不是固定号码。 例如,我运行该代码的最后3次,我得到63202、64025、62920。
1回复

为什么简单的Parallel.ForEach有时无法为字符串变量添加值?

我试图加速foreach循环,它将方法的字符串结果追加到变量。 结果字符串被附加到变量的顺序无关紧要,所以我考虑将foreach更改为Parallel.ForEach。 运行一个简单的测试我发现这可能导致一个或多个结果字符串无法附加到变量。 这是我创建的测试。 现在,如果我多次
2回复

.Net 中的多个 Parallel.ForEach 循环

在 .Net 进程中,只有一个托管线程池。 我们可以根据需要通过公共属性设置最小和最大线程数。 在 .Net 中,我们还有Parallel.ForEach ,它在后台从这个托管线程池中获取线程。 在Parallel.ForEach我们还可以设置MaxDegreeOfParallelism来限制最
5回复

打破parallel.foreach?

如何跳出parallel.for循环? 我有一个非常复杂的语句,如下所示: 使用并行类,我可以优化这个过程。 然而; 我不知道如何打破并行循环? break; 语句抛出以下语法错误: 没有可以中断或继续的封闭循环