PLINQ ForAll在.NET 4.0和4.5中打破

时间:2012-05-22 16:54:48

标签: .net multithreading linq task plinq

我正试图想出一种方法来加速以最快的方式组合列表中包含的大量对象。希望利用PLINQ,我尝试了它,但这不是一个线程安全的解决方案。我已经在4.0和4.5中测试了VS2010和VS11Beta。这是我的示例应用。如果你在1-500之间改变BlowUp(),它通常会起作用。 500后轮子来自赛道。它会在多个地方失败。有谁知道解决这个问题的最快方法? (多维数组+ PLINQ?)

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

namespace PLinqBlowsUp
{
class Program
{
    static void Main(string[] args)
    {
        BlowUp(5000);
    }

    private static void BlowUp(int blowupNum)
    {
        try
        {
            var theExistingMasterListOfAllRowsOfData = new List<List<KeyValuePair<string, dynamic>>>();

            //Add some test data
            Enumerable.Range(0, blowupNum).AsParallel().ForAll(row => theExistingMasterListOfAllRowsOfData.Add(AddRowToMasterList(row)));


            var aNewRowOfData = new List<KeyValuePair<string, dynamic>>();
            //Add some test data
            var column = new KeyValuePair<string, dynamic>("Title", "MyTitle");
            aNewRowOfData.Add(column);

            var anotherNewRowOfData = new List<KeyValuePair<string, dynamic>>();
            //Add some test data
            var columnA = new KeyValuePair<string, dynamic>("Date", DateTime.Now);
            var columnB = new KeyValuePair<string, dynamic>("ImportantColumn", "ImportantData");
            var columnC = new KeyValuePair<string, dynamic>("VeryImportantColumn", "VeryImportantData");
            anotherNewRowOfData.Add(columnA);
            anotherNewRowOfData.Add(columnB);
            anotherNewRowOfData.Add(columnC);

            //Now the Problem
            aNewRowOfData.AsParallel().ForAll(anrod => theExistingMasterListOfAllRowsOfData.ForEach(temloarod => temloarod.Add(anrod)));
            anotherNewRowOfData.AsParallel().ForAll(anrod => theExistingMasterListOfAllRowsOfData.ForEach(temloarod => temloarod.Add(anrod)));

            //Test for number
            foreach (var masterRow in theExistingMasterListOfAllRowsOfData)
            {
                if (masterRow.Count != 7)
                    throw new Exception("BLOW UP!!!");
            }
        }
        catch (AggregateException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    private static List<KeyValuePair<string, dynamic>> AddRowToMasterList(int row)
    {
        var columnA = new KeyValuePair<string, dynamic>("FirstName", "John" + row.ToString());
        var columnB = new KeyValuePair<string, dynamic>("LastName", "Smith" + row.ToString());
        var columnC = new KeyValuePair<string, dynamic>("Ssn", 123456789 + (row*10));

        var list = new List<KeyValuePair<string, dynamic>>();
        list.Add(columnA);
        list.Add(columnB);
        list.Add(columnC);
        return list;
    }
}
}

3 个答案:

答案 0 :(得分:4)

这与PLinq无关 - 向List<T>添加项目根本不是线程安全的。一个可行的解决方案会降低性能,但会引入锁定。你通常想要做的是将你的PLinq语句投射到 new 集合中 - 像你一样引入副作用而不是Linq /函数式编程的精神,而不是你可能遇到麻烦(正如你所做的那样)。

答案 1 :(得分:3)

我看到两个问题。

  • 您正在从多个线程调用Add实例上的theExistingMasterListOfAllRowsOfData,而不尝试同步访问它。
  • 您正在调用来自多个线程的单个Add项目的List<KeyValuePair<string, dynamic>>,而不尝试同步它们。

您可以使用lock来保护Add方法或使用ConcurrentBag代替。但是,这些选项都不是那么好。这里的问题是这种操作无法很好地并行化,因为所有线程最终都会竞争相同的锁。我非常怀疑,即使是低锁ConcurrentBag也会比你刚刚在PLINQ上踩踏并且在主线程上完成所有事情一样慢。

答案 2 :(得分:2)

PLinq不能替代编写线程安全的代码。您对theExistingMasterListOfAllRowsOfData的访问权限不是线程安全的,因为线程池中的所有线程都会访问它。你可以尝试锁定它,这解决了我的问题:

Enumerable.Range(0, blowupNum).AsParallel().ForAll(row => {
    lock (theExistingMasterListOfAllRowsOfData) {                 
        theExistingMasterListOfAllRowsOfData.Add(AddRowToMasterList(row));
    }
});

然而,锁定可能不是您所追求的,因为这会引入瓶颈。