对于不处理所有元素,这是如何并行的?

How is this parallel for not processing all elements?

我已经创建了这个正常的 for 循环:

    public static Dictionary<string,Dictionary<string,bool>> AnalyzeFiles(IEnumerable<string> files, IEnumerable<string> dependencies)
    {
        Dictionary<string, Dictionary<string, bool>> filesAnalyzed = new Dictionary<string, Dictionary<string, bool>>();
        foreach (var item in files)
        {
            filesAnalyzed[item] = AnalyzeFile(item, dependencies);
        }
        return filesAnalyzed;
    }

循环只检查变量 "files" 中的每个文件是否具有变量 "dependencies" 中指定的所有依赖项。

"files" 变量应该只包含唯一元素,因为它被用作结果的键,一个字典,但我在调用方法之前检查了这一点。

for 循环工作正常,所有元素都在单线程中处理,所以我想通过更改为并行 for 循环来提高性能,问题是并非所有元素都来自 "files" 变量正在并行处理(在我的测试用例中,我得到 30 个元素而不是 53 个)。

我尝试增加时间跨度,或者删除所有 "Monitor.TryEnter" 代码并仅使用一个锁 (filesAnalyzed),但仍然得到相同的结果

我对 paraller for 不是很熟悉,所以它可能与我使用的语法有关。

    public static Dictionary<string,Dictionary<string,bool>> AnalyzeFiles(IEnumerable<string> files, IEnumerable<string> dependencies)
    {
        var filesAnalyzed = new Dictionary<string, Dictionary<string, bool>>();

        Parallel.For<KeyValuePair<string, Dictionary<string, bool>>>(
            //start index
            0,
            //end index
            files.Count(),
            // initialization?
            ()=>new KeyValuePair<string, Dictionary<string, bool>>(),
            (index, loop, result) =>
            {
                var temp = new KeyValuePair<string, Dictionary<string, bool>>(
                               files.ElementAt(index),
                               AnalyzeFile(files.ElementAt(index), dependencies));
                return temp;
            }
            ,
            //finally
            (x) =>
            {
                if (Monitor.TryEnter(filesAnalyzed, new TimeSpan(0, 0, 30)))
                {
                    try
                    {
                        filesAnalyzed.Add(x.Key, x.Value);
                    }
                    finally
                    {
                        Monitor.Exit(filesAnalyzed);
                    }
                }
            }
            );
        return filesAnalyzed;
    }

欢迎任何反馈

以这种方式重写您的正常循环:

   Parallel.Foreach(files, item=>
    {
        filesAnalyzed[item] = AnalyzeFile(item, dependencies);
    });

您还应该使用 ConcurrentDictionary 除了 Dictionary 以使所有进程线程安全

如果改用并行 LINQ,可以将代码简化 很多

public static Dictionary<string,Dictionary<string,bool>> AnalyzeFiles(IEnumerable<string> files, IEnumerable<string> dependencies)
{
    var filesAnalyzed = ( from item in files.AsParallel()
                          let result=AnalyzeFile(item, dependencies)
                          select (Item:item,Result:result)
                        ).ToDictionary( it=>it.Item,it=>it.Result)               
    return filesAnalyzed;
}

我在这种情况下使用元组语法来避免噪音。它还减少了分配。

使用方法语法,同样可以写成:

var filesAnalyzed = files.AsParallel()
                         .Select(item=> (item, AnalyzeFile(item, dependencies)))
                         .ToDictionary( it=>it.Item,it=>it.Result)               

Dictionary<> 修改不是线程安全的。如果您想在不锁定的情况下使用 Parallel.ForEach,则必须使用 ConcurrentDictionary

var filesAnalyzed = ConcurrentDictionary<string,Dictionary<string,bool>>;

Parallel.ForEach(files,file => {
    filesAnalyzed[item] = AnalyzeFile(item, dependencies);
});

至少在这种情况下,使用 Parallel 而不是 PLINQ 没有任何好处。

假设 AnalyzeFiledependencies 中的代码是线程安全的,那么这样的代码怎么样:

var filesAnalyzed = files
    .AsParellel()
    .Select(x => new{Item = x, File = AnalyzeFile(x, dependencies)})
    .ToDictionary(x => x.Item, x=> x.File);

如果不调试代码,很难说到底出了什么问题。只是看看它,尽管我会为 filesAnalyzed 变量使用 ConcurrentDictionary 而不是普通的 `Dictionary 并摆脱监视器。

我还会检查字典文件中是否已存在相同的键已分析,可能是您正在尝试使用已添加到字典中的键添加 kvp。