8

多数のスレッドが並行辞書を並行して更新し、メインスレッドが一定の時間間隔の後、すべての更新スレッドが完了するまで辞書の状態をソートされた順序で表示する次のプログラムで、再現が困難なエラーが発生しています。

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);
    }            
}

関数には、文字ストリームと出力子のリストが与えられます。この関数は、各文字ストリームから読み取られた単語の単語頻度の同時辞書を(並列に)維持します。単語は新しいスレッドによって読み込まれ、メインスレッドは、すべての入力ストリームが読み込まれるまで、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();
}

ほとんどの実行は問題ありませんが、WriteBatch関数のforeachステートメントで次のエラーが発生することがあります。

「未処理の例外:System.ArgumentException:インデックスが配列の長さ以上であるか、ディクショナリ内の要素の数が、インデックスから宛先配列の最後までの使用可能なスペースよりも大きいです。」

スレッドの更新を開始した後、表示ループを開始する前にメインスレッドが短時間スリープすると、エラーは解消されたように見えます。また、orderby句が削除され、辞書がlinqクエリで並べ替えられていない場合も、なくなるようです。説明はありますか?

foreach (var tuple in batch)WriteBatch関数のステートメントはエラーを出します。スタックトレースは次のとおりです。

未処理の例外:System.ArgumentException:インデックスが配列の長さ以上であるか、ディクショナリ内の要素の数が、インデックスから宛先配列の最後までの使用可能なスペースよりも大きいです。System.Linq.Buffer1..ctor(IEnumerable1 source)atSystem.Linq.OrderedEnumerable1のSystem.Collections.Concurrent.ConcurrentDictionary2.System.Collections.Generic.ICollection> .CopyTo(K eyValuePair2 [] array、Int32 index)で。 d__0.MoveNext()at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext()at MyProject.ConsoleOutputter.WriteBatch(IEnumerable1 batch)in C:\ MyProject \ ConsoleOutputter.cs:line 10 at MyProject.Function(IEnumerable1 characterReaders、IOutputter outputter)

4

2 に答える 2

13

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);
    }
}

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);
}            
于 2012-07-28T09:21:29.457 に答える
1

コメントでChrisShainと話し合った後、結論として、辞書を印刷する前mutexに、lockステートメントのいずれかを使用して、辞書に相互に排他的にアクセスできるようにする必要があります。

ロックでそれを行う:

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

myLockクラスレベルでオブジェクトを割り当てたと仮定します。を参照してください。

ミューテックスでそれを行う:

public void WriteBatch(IEnumerable<Tuple<string, int>> batch)
{
    mut.WaitOne();

    foreach (var tuple in batch)
    {
        Console.WriteLine("{0} - {1}", tuple.Item1, tuple.Item2);
    }
    Console.WriteLine();

    mut.ReleaseMutex();
}

Mutexここでも、クラスレベルでオブジェクトを割り当てたと仮定します。を参照してください。

于 2012-07-27T17:52:27.797 に答える