PLINQ For Все сломано в.NET 4.0 и 4.5

Я пытаюсь найти способ как можно быстрее объединить огромное количество объектов, содержащихся в списках. Надеясь воспользоваться PLINQ, я попытался, но это не многопоточное решение. Я тестировал в VS2010 и VS11Beta в 4.0 и 4.5. Это мой пример приложения. Если вы измените BlowUp() на 1-500, он обычно будет работать. После 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 ответа

Решение

Я вижу две проблемы.

  • Ты звонишь Add на theExistingMasterListOfAllRowsOfData Экземпляр из более чем одного потока без какой-либо попытки синхронизировать доступ к нему.
  • Ты звонишь Add на человека List<KeyValuePair<string, dynamic>> элементы из более чем одного потока без какой-либо попытки синхронизировать их.

Вы могли бы использовать lock защищать Add методы или использовать ConcurrentBag вместо. Однако ни один из этих вариантов не настолько хорош. Проблема здесь в том, что такого рода операции нельзя распараллелить очень хорошо, потому что все потоки в конечном итоге будут бороться за одну и ту же блокировку. Я очень подозреваю, что даже низкий замок ConcurrentBag будет медленнее, чем если бы вы просто набросились на PLINQ и сделали все для основного потока с самого начала.

Это не имеет ничего общего с PLinq - добавление элемента в List<T> просто не является потокобезопасным. Реальным решением, которое ухудшит производительность, будет введение блокировки. Вместо этого вы обычно хотели бы проецировать на новую коллекцию в результате вашего утверждения PLinq - вводить побочные эффекты, как вы это сделали, скорее не в духе Linq / функционального программирования в целом, и вы можете столкнуться с проблемами (так как ты сделал).

PLinq не заменяет написание потокобезопасного кода. Ваш доступ к theExistingMasterListOfAllRowsOfData не является потокобезопасным, так как к нему обращаются все потоки из пула потоков. Вы можете попробовать заблокировать его, что решило проблему для меня:

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

Однако блокировка может быть не тем, что вам нужно, поскольку это приведет к появлению узких мест.

Другие вопросы по тегам