同步集合 InvalidOperationException/System.ArgumentException

3

我编写了一些类来使用SynchronizedCollection测试多线程。

class MultithreadTesting
{
    public readonly SynchronizedCollection<int> testlist = new SynchronizedCollection<int>();
    public SynchronizedReadOnlyCollection<int> pubReadOnlyProperty
    {
        get
        {
            return new SynchronizedReadOnlyCollection<int>(testlist.SyncRoot, testlist);
        }
    }

    public void Test()
    {
        int numthreads = 20;
        Thread[] threads = new Thread[numthreads];
        List<Task> taskList = new List<Task>();
        for (int i = 0; i < numthreads / 2; i++)
        {
            taskList.Add(Task.Factory.StartNew(() =>
            {
                for (int j = 0; j < 100000; j++)
                {
                    testlist.Add(42);
                }
            }));
        }

        for (int i = numthreads / 2; i < numthreads; i++)
        {
            taskList.Add(Task.Factory.StartNew(() =>
            {
                var sum = 0;
                foreach (int num in pubReadOnlyProperty)
                {
                    sum += num;
                }
            }));
        }
        Task.WaitAll(taskList.ToArray());
        testlist.Clear();
    }
}

我使用以下方式来运行它

    MultithreadTesting test = new MultithreadTesting();
    while (true)
        test.Test();

但是代码抛出了异常:System.ArgumentException:“目标数组长度不够。请检查 destIndex 和 length,以及数组的下限。”

如果我尝试在foreach中使用testlist,我会得到以下错误:

System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'

然而,MSDN 告诉我们:

SynchronizedReadOnlyCollection 类

提供一个线程安全的只读集合,其中包含由泛型参数指定类型的对象作为元素。

1个回答

2
错误的根本原因是 List<T> 构造不是线程安全的。
让我们看看当构建新的 SynchronizedReadOnlyCollection 时会发生什么。异常发生在以下行中:
return new SynchronizedReadOnlyCollection<int>(testlist.SyncRoot, testlist);

根据异常StackTrace的提示,构造过程中涉及到List<T>..ctor:

最初的回答:

at System.Collections.Generic.SynchronizedCollection`1.CopyTo(T[] array, Int32 index)
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Collections.Generic.SynchronizedReadOnlyCollection`1..ctor(Object syncRoot, IEnumerable`1 list)

以下摘自List<T>构造函数的代码片段显示了错误发生的位置。 代码从MS参考源复制,我删除了不必要的代码以便更容易阅读。 请注意,在注释(1)和(2)之间有其他线程操作集合: "Original Answer"
public List(IEnumerable<T> collection) {
    ICollection<T> c = collection as ICollection<T>;
    // (1) count is now current Count of collection
    int count = c.Count;
    // other threads can modify collection meanwhile
    if (count == 0)
    {
        _items = _emptyArray;
    }
    else {
        _items = new T[count];
        // (2) SynchronizedCollection.CopyTo is called (which itself is thread-safe)
        // Collection can still be modified between (1) and (2) 
        // No when _items.Count != c.Count -> Exception is raised.
        c.CopyTo(_items, 0);
        _size = count;
    }
}

解决方案

问题可以通过在构建新的SynchronizedReadOnlyCollection时锁定testlist修改来轻松解决。

最初的回答

public SynchronizedReadOnlyCollection<int> pubReadOnlyProperty
{
    get
    {
        lock (testlist.SyncRoot)
        {
            return new SynchronizedReadOnlyCollection<int>(testlist.SyncRoot, testlist);
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接