在更新时排序和显示时,在并发字典中获取参数异常

San*_* Rm 4 c# concurrency multithreading race-condition concurrentdictionary

我得到一个难以复制的错误在下面的程序中,更新平行,主线程并发字典中的线程数显示后固定的时间间隔字典排序顺序的状态,直到所有的更新线程完成.

public void Function(IEnumerable<ICharacterReader> characterReaders, IOutputter outputter)
{
    ConcurrentDictionary<string, int> wordFrequencies = new ConcurrentDictionary<string, int>();
    Thread t = new Thread(() => UpdateWordFrequencies(characterReaders, wordFrequencies));
    bool completed = false;
    var q = from pair in wordFrequencies orderby pair.Value descending, pair.Key select new Tuple<string, int>(pair.Key, pair.Value);
    t.Start();
    Thread.Sleep(0);

    while (!completed)
    {
        completed = t.Join(1);
        outputter.WriteBatch(q);
    }            
}
Run Code Online (Sandbox Code Playgroud)

该函数给出了字符流和输出器的列表.该函数维护从每个字符流中读取的字的字频的并发字典(并行).的文字,被一个新的线程读入,并且,直到所有的输入流已被读出的主线程输出词典的当前状态(以排序的顺序),每1毫秒(在实践中,输出将是什么每10秒等,但错误似乎只出现在非常小的值上.WriteBatch函数只是写入控制台:

public void WriteBatch(IEnumerable<Tuple<string, int>> batch)
{
    foreach (var tuple in batch)
    {
        Console.WriteLine("{0} - {1}", tuple.Item1, tuple.Item2);
    }
    Console.WriteLine();
}
Run Code Online (Sandbox Code Playgroud)

大多数执行都很好,但有时我在WriteBatch函数的foreach语句中得到以下错误:

"未处理的异常:System.ArgumentException:索引等于或大于数组的长度,或者字典中的元素数量比索引到目标数组末尾的可用空间大."

如果主线程在启动更新线程之后和启动显示循环之前暂停一段时间,则错误确实会消失.如果删除了orderby子句并且未在linq查询中对字典进行排序,它似乎也会消失.有什么解释吗?

foreach (var tuple in batch)WriteBatch函数中的语句给出了错误.堆栈跟踪如下:

未处理的异常:System.ArgumentException:索引等于或大于数组的长度,或者字典中的元素数比从索引到目标数组末尾的可用空间更大.在System.Linq.OrderedEnumerable1的System.Linq.Buffer1..ctor(IEnumerable1 source)的System.Collections.Concurrent.ConcurrentDictionary2.System.Collections.Ge neric.ICollection> .CopyTo(K eyValuePair2 []数组,Int32索引)处. d__0.MoveNext()位于MyProject.Function上的MyProject.ConsoleOutputter.WriteBatch(IEnumerable1批处理)中的System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext():MyProject.Function的第10行(IEnumerable1 characterReaders,IOutputter outputter)

Nic*_*ler 7

正如其他人所说,内部类的构造函数中存在一个竞争System.Linq.Buffer<T>,它被称为OrderBy.

这是违规的代码段:

TElement[] array = null;
int num = 0;
if (collection != null)
{
    num = collection.Count;
    if (num > 0)
    {
        array = new TElement[num];
        collection.CopyTo(array, 0);
    }
}
Run Code Online (Sandbox Code Playgroud)

将项目添加到collection调用之后但调用collection.Count之前,抛出异常collection.CopyTo.


作为一种解决方法,您可以在排序之前制作字典的"快照"副本.

你可以通过调用来实现这一点.ConcurrentDictionary.ToArray.
这是在ConcurrentDictionary类本身实现的,它是安全的.

使用这种方法意味着您不必使用锁来保护集合,正如您所说的那样,它首先会破坏使用并发集合的目的.

while (!completed)
{
    completed = t.Join(1);

    var q =
      from pair in wordFrequencies.ToArray() // <-- add ToArray here
      orderby pair.Value descending, pair.Key
      select new Tuple<string, int>(pair.Key, pair.Value);

    outputter.WriteBatch(q);
}            
Run Code Online (Sandbox Code Playgroud)

  • +1,谢谢。似乎甚至`ToList()` 也具有相同的竞争条件,(具有讽刺意味的是)我首先尝试使用它来创建快照。 (2认同)