13

次のように、foreach を使用して要素のリストをループしています。

foreach (Type name in aList) {
   name.doSomething();
}

ただし、別のスレッドで次のようなものを呼び出しています

aList.Remove(Element);

実行時に、これにより InvalidOperationException: Collection was modified; が発生します。列挙操作が実行されない場合があります。これを処理する最良の方法は何ですか (パフォーマンスを犠牲にしても、かなり単純にすることをお勧めします)?

ありがとう!

4

6 に答える 6

16

これを処理する最良の方法は何ですか (パフォーマンスを犠牲にしても、かなり単純にすることをお勧めします)?

基本的に、ロックせずに複数のスレッドからスレッドセーフでないコレクションを変更しようとしないでください。反復しているという事実は、ここではほとんど無関係です。それは、それをより速く見つけるのに役立ちました。2 つのスレッドが同時に呼び出されるのは安全ではありませんでしRemoveた。

のようなスレッドセーフなコレクションを使用する一度に 1 つのスレッドだけがコレクションに対して何らかの処理を行うようにしてください。ConcurrentBag

于 2012-04-04T19:19:54.080 に答える
12

方法 #1:

最も単純で最も効率の悪い方法は、リーダーとライターのクリティカル セクションを作成することです。

// Writer
lock (aList)
{
  aList.Remove(item);
}

// Reader
lock (aList)
{
  foreach (T name in aList)
  {
    name.doSomething();
  }
}

方法 #2:

これは方法 1 に似ていforeachますが、ループの全期間にわたってロックを保持する代わりに、最初にコレクションをコピーしてからコピーを反復処理します。

// Writer
lock (aList)
{
  aList.Remove(item);
}

// Reader
List<T> copy;
lock (aList)
{
  copy = new List<T>(aList);
}
foreach (T name in copy)
{
  name.doSomething();
}

方法 #3:

それはすべて特定の状況に依存しますが、私が通常これに対処する方法は、コレクションへのマスター参照を不変に保つことです。そうすれば、リーダー側でアクセスを同期する必要はありません。ライター側にはlock. リーダー側には何も必要ありません。つまり、リーダーは高度な並行性を維持します。あなたがする必要がある唯一のことは、aList参照を としてマークすることですvolatile

// Variable declaration
object lockref = new object();
volatile List<T> aList = new List<T>();

// Writer
lock (lockref)
{
  var copy = new List<T>(aList);
  copy.Remove(item);
  aList = copy;
}

// Reader
List<T> local = aList;
foreach (T name in local)
{
  name.doSomething();
}
于 2012-04-04T20:01:06.430 に答える
11

スレッド A:

lock (aList) {
  foreach (Type name in aList) {
     name.doSomething();
  }
}

スレッド B:

lock (aList) {
  aList.Remove(Element);
}

もちろん、これはパフォーマンスにとって本当に悪いことです。

于 2012-04-04T19:19:25.533 に答える
1

複数のリーダーがある場合は、リーダーライターロック(.Net 3.5以降)、スリムを試してください:http: //msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx

Eugen Rieckの回答に示されているように、リーダーが1つしかない場合は、リスト自体またはプライベートオブジェクトをロックするだけです(ただし、タイプ自体はロックしないでください)。

于 2012-04-04T19:36:22.363 に答える
0

あなたの質問からは具体的にわかりませんが、(どうやら)各アイテムに対してアクションを実行してから削除しているようです。を調べて、自分に適しているかどうかを確認BlockingCollection<T>するメソッド呼び出しを行うことをお勧めします。GetConsumingEnumerable()ここに小さなサンプルがあります。

void SomeMethod()
{
    BlockingCollection<int> col = new BlockingCollection<int>();

    Task.StartNew( () => { 

        for (int j = 0; j < 50; j++)
        {
            col.Add(j);
        }

        col.CompleteAdding(); 

     });

    foreach (var item in col.GetConsumingEnumerable())
    {
       //item is removed from the collection here, do something
       Console.WriteLine(item);
    }
}
于 2012-04-04T19:33:37.800 に答える
0

例外の使用を避けたいだけの場合

foreach (Type name in aList.ToArray()) 
{ name.doSomething(); }

要素が他のスレッドで削除された場合にも doSomething() が実行されることに注意してください

于 2012-04-04T19:31:40.287 に答える