27

この質問にはすでに答えがあります。

だから私は直接イベントを呼び出すのではなく、それについて読みました

if (SomeEvent != null)
   SomeEvent(this, null);

すべきだ

SomeEventHandler temp = SomeEvent;
if (temp != null)
    temp(this, null);

これはなぜですか。 2番目のバージョンはどのようにスレッドセーフになりますか?ベストプラクティスは何ですか?


  • ここで、暫定的で修飾された答えを読み進めると、C#でのイベント処理は密接に関連し、エラーが発生しやすく、よく理解されていないということがわかります。 - micahhoover

4 답변


14

イベントは、実際にはデリゲートのリストに対する構文上の糖です。イベントを呼び出すとき、これは実際にそのリストを反復処理して、渡されたパラメータを使用して各デリゲートを呼び出すことです。

スレッドの問題点は、購読/購読解除によってこのコレクションにアイテムを追加または削除できることです。あなたがコレクションを繰り返している間にそれらがこれをした場合、これは問題を引き起こすでしょう(私は例外が投げられると思います)

その目的は、リストを繰り返す前にリストをコピーすることであるため、リストに対する変更から保護されています。

注:ただし、購読を解除した後もリスナーを起動できるようになったため、リスナーコードでこれを確実に処理するようにしてください。


  • 実際には、コレクションを反復処理している間は問題にはなりません。ここで働いている参加者は不変です。唯一の問題は、誰かが登録して実際にイベントハンドラを呼び出しているかどうかを確認する前の一瞬です。実行を開始した後は、背景の変更は現在の呼び出しに影響しません。 - Lasse Vågsæther Karlsen

30

IMO、他の答えでは、1つの重要な詳細が欠けています。不変。これの重要な点は、イベントハンドラを購読または購読解除することです。しない単にリストに追加/削除するだけです。置き換えます追加の(または1つ少ない)アイテムを含む新しいリストのリスト。

参照はアトミックであるので、これはあなたがする時点でそれが意味する:

var handler = SomeEvent;

あなたは今持っています硬い次のピコ秒で別のスレッドが購読を中止したとしても、変更できないインスタンス(実際のになるイベントフィールドnull

ですから、nullをテストしてそれを呼び出すだけで、すべて問題ありません。もちろんありますそれでもイベントの混乱を招くシナリオ上げたピコ秒前に退会したと思われるオブジェクトに!


  • これは1つの懸念に対処しますが、置き換え自体は、要求されたすべての操作が行われることを保証するような方法で安全に行われていますか?つまり、2つのスレッドが同時に別々のデリゲートをサブスクライブしようとした場合、両方が最後にサブスクライブされることは保証されますか、それともどちらか一方のサブスクリプションが暗黙のうちに失敗する可能性はありますか? - binki
  • @ binkiはい。それはインターロックされた交換ループ(現代のコンパイラ)、または同期(交換)を使いますlock)地域(古いコンパイラ) - Marc Gravell

5

ベストプラクティスは2番目の形式です。その理由は、他のスレッドがnullまたは変更する可能性があるためSomeEvent間に 'ifテストと呼び出し。


  • 2番目のステートメントでなぜそれができないのですか。 - Daniel
  • 読むのはSomeEventアトミックです。つまり、すべての方法で発生するか、まったく発生しません。このようにtempローカルであるため、そのスレッドの外側では変更できません。 - user7116
  • おっとs/Thus/Also/ - user7116

2

ここに.NETイベントとスレッドとの競合状態についての良い記事です。それはいくつかの一般的なシナリオをカバーし、それにいくつかの良い参考文献があります。

お役に立てれば。


  • リンクをありがとう - Daniel

リンクされた質問


関連する質問

最近の質問