0

When you new up a new list in C# in multiple threads, does each thread get its own instance of a list?

So my code looks similar to this:

List<Item> _items = new List<Item>();

public void Method(List<List<Batch>> batches) {
    Parallel.ForEach(batches, parallelOptions, (batch, state) => CalculateAndAddToList(batch))
}

private void CalculateAndAddToList(List<Batch> batch) {
 //Some computations
    var localItems = new List<Item>();
    foreach(var b in batch) {
        //Computations of newItems
        localItems.AddRange(newItems);
    }
    _items.AddRange(items);
}

I will randomnly get an error that says: "Destination array was not long enough. Check destIndex and length, and the array's lower bounds." at the line that says: "localItems.AddRange(newItems)". Since each thread is newing up its own list instance of localitems, I figured other threads wouldn't be changing it, so why am getting that error on that line?

I figured I'd need a lock around "_items.AddRange(items)" since multiple threads access and change the shared field "_items", but I have yet to get a threading error to throw on that line.

4

2 回答 2

5

It's fine for each thread to have it's own list. The problem you are seeing stems from this line:

_items.AddRange(items);

is potentially being called from two threads at the same time. List<T> is not thread-safe. Use a threadsafe container, such as ConcurrentBag<T>, or better still, restate your problem using PLINQ, and allow the framework to safely gather your results for you. You could probably save on quite a lot of code by avoiding partitioning your source data into batches and stating your transformation in parallel linq. It does all of this for you.

http://blogs.msdn.com/b/pfxteam/archive/2009/05/28/9648672.aspx

于 2013-11-05T00:50:16.063 回答
3

由于List<T>不是线程安全的,因此您需要在调用AddRange列表之前锁定:

private void CalculateAndAddToList(List<Batch> batch) {
 //Some computations
    var localItems = new List<Item>();
    foreach(var b in batch) {
        //Computations of newItems
        localItems.AddRange(newItems);
    }

    lock(_items)
        _items.AddRange(items);
}

更好的方法是使用其中一个Parallel.ForEach接受本地状态的重载,然后在最后添加项目。基本过程在我关于该主题的博客文章中有所介绍。

或者,这似乎可以通过 PLINQ 查询使用SelectMany

_items = batches.AsParallel()
                .SelectMany(batch => batch) // Flatten List<List<T>> into List<T>
                .SelectMany(b => ComputeNewItems(b)) // Compute new items, and flatten results                    
                .ToList();
于 2013-11-05T00:51:21.677 回答