c# - 并行嵌套操作返回奇怪的结果

标签 c# .net parallel-processing task-parallel-library

我正在尝试在我的代码中使用并行库,但遇到了一个奇怪的问题。 我编写了一个简短的程序来演示该行为。简而言之,我做了两个循环(一个循环在另一个循环内)。第一个循环生成一个包含 200 个整数的随机数组,第二个循环将所有数组添加到一个大列表中。 问题是,最终我没有得到 200 整数的倍数,而是看到一些运行没有等待随机数组完全加载。 很难解释,所以这里是示例代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TestParallel
{
    class Program
    {
        static int RecommendedDegreesOfParallelism = 8;
        static int DefaultMaxPageSize = 200;

        static void Main(string[] args)
        {
            int maxPage = 50;
            List<int> lstData = new List<int>();
            Parallel.For(0, RecommendedDegreesOfParallelism, new ParallelOptions() { MaxDegreeOfParallelism = RecommendedDegreesOfParallelism },
                (index) =>
                {
                    int cptItems = 0;
                    int cptPage = 1 - RecommendedDegreesOfParallelism + index;
                    int idx = index;
                    do
                    {
                        cptPage += RecommendedDegreesOfParallelism;
                        if (cptPage > maxPage) break;

                        int Min = 0;
                        int Max = 20;
                        Random randNum = new Random();
                        int[] test2 = Enumerable
                            .Repeat(0, DefaultMaxPageSize)
                            .Select(i => randNum.Next(Min, Max))
                            .ToArray();
                        var lstItems = new List<int>();
                        lstItems.AddRange(test2);
                        var lstRes = new List<int>();
                        lstItems.AsParallel().WithDegreeOfParallelism(8).ForAll((item) =>
                        {
                            lstRes.Add(item);
                        });

                        Console.WriteLine($"{Task.CurrentId} = {lstRes.Count}");
                        lstData.AddRange(lstRes);
                        cptItems = lstRes.Count;
                    } while (cptItems == DefaultMaxPageSize);
                }
            );
            Console.WriteLine($"END: {lstData.Count}");
            Console.ReadKey();
        }
    }
}

这是执行日志:

4 = 200
1 = 200
2 = 200
3 = 200
6 = 200
5 = 200
7 = 200
8 = 200
1 = 200
6 = 194
2 = 191
5 = 200
7 = 200
8 = 200
4 = 200
5 = 200
3 = 182
4 = 176
8 = 150
7 = 200
5 = 147
1 = 200
7 = 189
1 = 200
1 = 198
END: 4827

我们可以看到一些循环返回的项目少于 200 个。 怎么可能?

最佳答案

这里不是线程安全的:

lstItems.AsParallel().WithDegreeOfParallelism(8).ForAll((item) =>
{
    lstRes.Add(item);
});

来自 List<T> 的文档:

It is safe to perform multiple read operations on a List, but issues can occur if the collection is modified while it's being read. To ensure thread safety, lock the collection during a read or write operation. To enable a collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

它没有明确提及,但 .Add()当多个线程同时调用时也可能失败。

解决方案是将调用锁定到 List<T>.Add()在上面的循环中,但如果这样做,它可能会比在单个线程的循环中添加项目慢。

var locker = new object();

lstItems.AsParallel().WithDegreeOfParallelism(8).ForAll((item) =>
{
    lock (locker)
    {
         lstRes.Add(item);
    }
});

关于c# - 并行嵌套操作返回奇怪的结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71265050/

相关文章:

c# - 以编程方式创建和启动 RDP session (无 gui)

Python 简单循环并行化 Jupyter Notebook

c# - XmlDocument 不保存字符实体

c# - 当 json 具有更深一层的数组时,如何使用 C# 将 Json 字符串转换为在 Json.Net 中具有列表的类

c# - 发生数据库错误后 DbContext 是否可以安全使用

c# - 我是否需要在每个实现 IDisposable 的对象中使用 "using"关键字?

c++ - 如何使用 openMP 将顺序程序转换为并行程序?

parameters - 当每个回调都有不同的参数时,将 Twisted Deferred 与并行回调一起使用

c# - WPF ListBox 布局(同时考虑所有行的动态列宽)

c# - 尝试执行 SQL 查询时出现无效对象错误