Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/260.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# Parallel.ForEach未按预期在C中的ConcurrentBag中添加项目_C#_Multithreading_Concurrency_Asp.net Core Webapi_Parallel.foreach - Fatal编程技术网

C# Parallel.ForEach未按预期在C中的ConcurrentBag中添加项目

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

在我的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的帮助下创建任务,以便同时执行它们

例如:

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