在多个线程中处理List时的空引用

时间:2012-09-14 09:36:10

标签: c# multithreading thread-safety

基本上,我有一个对象的集合,我把它切成小集合,并且同时在每个小集合上的一个线程上做一些工作。

int totalCount =  SomeDictionary.Values.ToList().Count;
int singleThreadCount = (int)Math.Round((decimal)(totalCount / 10));
int lastThreadCount = totalCount - (singleThreadCount * 9);

Stopwatch sw = new Stopwatch();

Dictionary<int,Thread> allThreads = new Dictionary<int,Thread>();
List<rCode> results = new List<rCode>();

for (int i = 0; i < 10; i++)
{
    int count = i;

    if (i != 9)
    {
        Thread someThread = new Thread(() =>
        {
            List<rBase> objects =  SomeDictionary.Values
                                          .Skip(count * singleThreadCount)
                                          .Take(singleThreadCount).ToList();

            List<rCode> result = objects.Where(r => r.ZBox != null)
            .SelectMany(r => r.EffectiveCBox, (r, CBox) => new rCode
                                {
                                    RBox = r,
                                    // A Zbox may refer an object that can be 
                                    // shared by many 
                                    // rCode objects even on different threads
                                    ZBox = r.ZBox,
                                    CBox = CBox
                                }).ToList();

            results.AddRange(result);
        });

        allThreads.Add(i, someThread);
        someThread.Start();
    }
    else 
    {
        Thread someThread = new Thread(() =>
        {
            List<rBase> objects =  SomeDictionary.Values
                                           .Skip(count * singleThreadCount)
                                           .Take(lastThreadCount).ToList();

            List<rCode> result = objects.Where(r => r.ZBox != null)
            .SelectMany(r => r.EffectiveCBox, (r, CBox) => new rCode
                        {
                            RBox = r,
                            // A Zbox may refer an object that 
                            // can be shared by many 
                            // rCode objects even on different threads
                            ZBox = r.ZBox, 
                            CBox = CBox
                        }).ToList();

            results.AddRange(result);
        });

        allThreads.Add(i, someThread);
        someThread.Start();
    }
}

sw.Start();
while (allThreads.Values.Any(th => th.IsAlive))
{ 
    if (sw.ElapsedMilliseconds >= 60000) 
    { 
        results = null; 
        allThreads.Values.ToList().ForEach(t => t.Abort()); 
        sw.Stop(); 
        break; 
    } 
}

return  results != null ? results.OrderBy(r => r.ZBox.Name).ToList():null;

所以,我的问题是SOMETIMES,我在返回结果之前执行OrderBy操作时得到一个空引用异常,我无法确定异常在哪里,我按回来,单击执行此操作的相同按钮再次操作相同的数据,它的工作原理!! ..如果有人可以帮助我确定这个问题,我将不仅仅是感激不尽。注意:Zbox可能会引用一个对象,即使在不同的线程上也可以被许多rCode对象共享,这可能是个问题吗? 因为我无法在测试中确定这一点,因为发生的错误不是确定性的。

3 个答案:

答案 0 :(得分:2)

虽然我不同意答案,但在所选答案中找到了错误。您应该切换到使用并发集合。在你的情况下是ConcurrentBag或ConcurrentQueue。其中一些(部分)无锁定以获得更好的性能。并且它们提供更多可读性和更少的代码,因为您不需要手动锁定。

如果您不使用手动创建的线程和手动分区,您的代码的大小也会减半,可读性也会增加一倍;

Parallel.ForEach(objects, MyObjectProcessor);

public void MyObjectProcessor(Object o)
{
  // Create result and add to results
}

如果要使用Parallel.ForEach限制线程数,请使用ParallelOptions对象............

答案 1 :(得分:1)

嗯,这里有一个明显的问题:

results.AddRange(result);

您要从多个线程更新列表。尝试使用锁:

object resultsLock = new object(); // globally visible
...
lock(resultsLock) 
{
    results.AddRange(result);
}

答案 2 :(得分:0)

我想结果中的问题= null

while (allThreads.Values.Any(th => th.IsAlive))
    { if (sw.ElapsedMilliseconds >= 60000) { results = null;  allThreads.Values.ToList().ForEach(t => t.Abort());

如果线程未超过60000毫秒,则结果将变为null,并且您无法调用results.OrderBy(r =&gt; r.ZBox.Name).ToList();它抛出异常

你应该添加类似的东西

if (results != null) 
  return  results.OrderBy(r => r.ZBox.Name).ToList();
else
  return null;