具有Parallel.ForEach的线程安全代码

时间:2018-08-25 13:34:13

标签: c# multithreading thread-safety

我很少接触我的代码的线程,但是面临减少运行时间的巨大压力,因此尝试使用parallel.ForEach循环。

代码对出价(项目)执行计算。这些项目已加载到List<List<Item>>中(外部列表是具有相同ID的项目,内部列表是各种项目)。我在课程末尾添加了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的出价的任何信息。 因此,我将出价分为结算期,以便在parallel.ForEach中运行计算。

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

但是,当我运行代码时,多次运行时,“ unParallel代码”与先前的输出结果不一致。这使我认为我的代码不是“线程安全的”,因为“ unParallel代码”既正确又一致(但是太慢了)。

此线程安全吗?我应该怎么做才能产生一致的结果?

我想知道我是否应该将日光锁定在一切之外...

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[]>();
}

****编辑****

作为对@ calum-mcveigh的响应,我使用以下代码段以及其他相关更改将列表更改为currentBags。但是,它仍然产生不一致的结果。我将完整的代码放在这里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 :(得分:1)

为确保线程安全,您可以使用诸如ConcurrentBag<T>之类的并发集合,而不要使用List<T>

您可以了解有关线程安全集合here

的更多信息

答案 1 :(得分:1)

变量queryItemsByAcceptIDParallel.ForEach外部声明,但在变量内部设置和使用。不再在那里寻找,但也许还有其他具有相同问题的变量。