Потокобезопасный код с Parallel.ForEach - PullRequest
0 голосов
/ 25 августа 2018

Я редко касаюсь многопоточности своим кодом, но столкнулся со значительным давлением, чтобы сократить время выполнения, и поэтому пытался использовать параллельный цикл. ForEach.

Код выполняет расчеты по заявкам (Предметам). Элементы были загружены в List<List<Item>> (внешний список представляет собой элементы с одинаковым идентификатором, а внутренний список представляет собой различные элементы). В конце я включил класс Item.

List<List<Item>> queryItemsByAcceptID = phyBidList.GroupBy(bids => bids.acceptID)
                        .Select(group => group.ToList())
                        .ToList();

Все заявки имеют свойство (расчетный период), которое разделяет их по времени, например период 1 = 00: 00-00: 30, период 2 = 00: 30-01: 00 и т. д. Ставка периода 2 не нуждается в информации от ставки периода 1. Поэтому я разделил заявки на периоды их расчета, чтобы выполнить вычисления в параллели. ForEach.

List<Item> phyBidList = new List<Item>();
var queryMassBySetPeriod = phyBidList.GroupBy(x => x.settlementPeriod)
                                .Select(group => group.ToList())
                                .ToList();

Однако, когда я запускаю код, я получаю противоречивые результаты как с «непараллельным кодом», так и с предыдущими выходными данными при многократном запуске. Это заставляет меня думать, что мой код не является «потокобезопасным», так как «непараллельный код» является правильным и непротиворечивым (но слишком медленным).

Безопасен ли этот поток? И что я должен сделать, чтобы получить последовательные результаты?

Мне интересно, должен ли я закрывать дневной свет из всего ...

acceptIdItem.FPN.Add(fpn);
acceptIdItem.qAboPosArea.Add(tempQABOposArea);
acceptIdItem.qAboNegArea.Add(tempQABOnegArea);

Однако я не уверен, подходит ли блокировка, потому что потоки не (или, по крайней мере, не должны) получать доступ к одним и тем же переменным ... и потому, что информация из других блоков не требуется, и ставки только идут через расчет один раз.

P.S. Я включил код ниже, я пытался удалить то, что не считаю необходимым, чтобы сделать его короче и легче для чтения.

Parallel.ForEach(queryMassBySetPeriod, block =>
{

Console.WriteLine("GroupBy UnitID");    
var queryItemsByUnitID = block.GroupBy(bids => bids.unitID)
                        .Select(group => group.ToList())
                        .ToList();

Console.WriteLine("GroupBy AcceptID");
queryItemsByAcceptID = block.GroupBy(bids => bids.acceptID)
                        .Select(group => group.ToList())
                        .ToList();

Console.WriteLine("Beginning mass interpretation...");
foreach (var list in queryItemsByAcceptID)
{    
    int bY = 0;
    foreach (var acceptIdItem in list)
    { 
        DateTime fromTime = acceptIdItem.fromTime;
        DateTime toTime = acceptIdItem.toTime;

        TimeSpan duration = toTime - fromTime;

        for (int i = 0; i < (duration.Minutes); i++) //qTime fix (duration.Minutes + 1)
        {  
            var queryPNdata = (from item in PNList
                            where item.unitID == acceptIdItem.unitID && item.fromTime <= fromTime && item.toTime >= fromTime
                            select item).FirstOrDefault();

            int time = (acceptIdItem.qTimes[i] - acceptIdItem.fromTime).Minutes;
            float boa = MathHelper.calcBOA(acceptIdItem.fromLevel, acceptIdItem.toLevel, (duration).Minutes, time);    
            float fpn = MathHelper.calcFPN(queryPNdata.fromLevel, queryPNdata.toLevel, duration.Minutes, time);

            acceptIdItem.qTimes.Add(fromTime + i * ((toTime - fromTime) / duration.Minutes));
            acceptIdItem.boa.Add(boa);
            acceptIdItem.FPN.Add(fpn);

            string[] tempBOUR = new string[6]; string[] tempBOLR = new string[6];
            float[] tempQABOneg = new float[6]; float[] tempQABOpos = new float[6];
            for (int k = 1; k < 7; k++)
            {
                //calculate tempBOUR/ tempBOLR/ tempQABOpos/ tempQABOneg
            }

            acceptIdItem.BOUR.Add(tempBOUR);
            acceptIdItem.BOLR.Add(tempBOLR);    
            acceptIdItem.qAboPos.Add(tempQABOpos);
            acceptIdItem.qAboNeg.Add(tempQABOneg);
        }

        int aZ = 0; //declared outside the loop to access later
        for (aZ = 0; aZ < (acceptIdItem.qAboNeg.Count() - 1); aZ++)
        {
            float[] tempQABOnegArea = new float[6]; float[] tempQABOposArea = new float[6];
            for (int k = 1; k < 7; k++)
            {
                //calculate tempQABOnegArea/ tempQABOposArea
            }

            acceptIdItem.qAboPosArea.Add(tempQABOposArea);
            acceptIdItem.qAboNegArea.Add(tempQABOnegArea);
        } 
        bY++;
    }
}
});

В начале назначается информация и класс добавляется в список (phyBidList). Вот этот класс ...

class Item
{
    public String unitID, acceptID, prevAcceptID, type;
    public DateTime fromTime, toTime, acceptTime;
    public int settlementPeriod, duration;
    public List<DateTime> qTimes = new List<DateTime>();
    public List<string[]> BOLR = new List<string[]>();
    public List<string[]> BOUR = new List<string[]>();
    public List<float[]> qAboNeg = new List<float[]>();
    public List<float[]> qAboPos = new List<float[]>();
    public List<float> boa = new List<float>();
    public List<float> prevBOA = new List<float>();
    public List<float> FPN = new List<float>();
    public List<float[]> qAboNegArea = new List<float[]>();
    public List<float[]> qAboPosArea = new List<float[]>();
}

**** ***** Редактировать 1025 *

В ответ на @ calum-mcveigh я изменил списки на concurrentBags с использованием приведенного ниже фрагмента и других соответствующих изменений. Тем не менее, это все еще дает противоречивые результаты. Я поставил полный код здесь https://pastebin.com/EgnE2285

ConcurrentBag<ConcurrentBag<Item>> queryMass = new ConcurrentBag<ConcurrentBag<Item>>();
foreach (var items in queryMassBySetPeriod)
{
    ConcurrentBag<Item> item = new ConcurrentBag<Item>();
    foreach (var bid in items)
    {                    
        item.Add(bid);
    }
    queryMass.Add(item);
}

Ответы [ 2 ]

0 голосов
/ 25 августа 2018

Переменная queryItemsByAcceptID объявлена ​​вне Parallel.ForEach, но установлена ​​и используется внутри нее.Перестал там смотреть, но, возможно, есть другие переменные с той же проблемой.

0 голосов
/ 25 августа 2018

Для обеспечения безопасности потоков вы можете использовать одновременную коллекцию, например ConcurrentBag<T>, а не List<T>

Подробнее о безопасных коллекциях можно прочитать здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...