[解決済み】C#のイベントとスレッドセーフについて
質問
アップデイト
C#6時点では。 答え となっています。
SomeEvent?.Invoke(this, e);
よく次のようなアドバイスを聞いたり読んだりします。
をチェックする前に、必ずイベントのコピーを作成します。
null
を実行します。これにより、スレッド化した場合に起こりうる、イベントが
null
を使用すると、NULLチェックとイベント実行のちょうど中間に位置することになります。
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
更新 : 最適化について読んでいて、これもイベントメンバをvolatileにする必要があるのではないかと思ったのですが、Jon Skeetは回答でCLRはコピーを最適化して取り除かないとしていますね。
しかし一方で、この問題が発生するためには、別のスレッドがこのようなことを行っているはずです。
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
実際の配列は、このように混在しているかもしれません。
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
という点です。
OnTheEvent
は作者が購読を解除した後に実行されますが、作者はそれを避けるために購読を解除しただけです。本当に必要なのは、カスタムイベントの実装と
add
と
remove
アクセサを使用します。さらに、イベントが発生している間、ロックが保持されるとデッドロックが発生する可能性があるという問題があります。
では、これは
カーゴカルトプログラミング
? 多くの人がマルチスレッドからコードを守るためにこのステップを踏んでいるようですが、実際には、マルチスレッド設計の一部として使用する前に、イベントはこれよりもはるかに多くの注意を払う必要があるように思います。その結果、そのような追加的な注意を払わない人は、このアドバイスを無視した方が良いでしょう - シングルスレッド・プログラムでは単に問題ではありません。
volatile
は、ほとんどのオンラインサンプルコードで、このアドバイスが全く効果を発揮していない可能性があります。
(そして、空の
delegate { }
をチェックする必要がないようにします。
null
というのは、そもそも?)
更新しました。 一応、助言の意図である「どんな状況でもヌル参照例外を回避する」ことは把握しました。私が言いたいのは、この特定のヌル参照例外は、他のスレッドがイベントからデリスティングする場合にのみ発生し、それを行う唯一の理由は、そのイベント経由でそれ以上のコールを受信しないことを保証することであり、明らかにこのテクニックでは達成されないということです。あなたはレースコンディションを隠していることになります。このNull例外は、コンポーネントの不正使用を検出するのに役立ちます。もし、コンポーネントを悪用から守りたいのであれば、WPFの例に従って、コンストラクタにスレッドIDを保存し、他のスレッドがコンポーネントと直接対話しようとしたら例外を投げるようにすればよいのです。あるいは、本当にスレッドセーフなコンポーネントを実装することです(簡単なことではありません)。
ですから、私は、このコピー/チェックのイディオムを行うだけでは、カーゴカルト・プログラミングであり、コードに混乱とノイズを加えることになると主張します。実際に他のスレッドから保護するためには、もっと多くの作業が必要です。
Eric Lippertのブログ記事を受けて更新。
イベントハンドラは、イベントの登録が解除された後でも呼び出されることに対して堅牢であることが要求されるため、明らかにイベントデリゲートが
null
.
イベントハンドラに関するそのような要件は、どこかに文書化されているのでしょうか?
例えば、ハンドラを初期化して、削除されない空のアクションを持たせるなどです。しかし、NULLチェックを行うのが標準的なパターンです。
というわけで、私の質問の残り1つの断片は。
なぜ explicit-null-check が "標準パターン" なのでしょうか?
空のデリゲートを割り当てるという代替案では、必要なのは以下の通りです。
= delegate {}
をイベント宣言に追加することで、イベントが発生するあらゆる場所から、あの臭いセレモニーの小山を排除することができるのです。空のデリゲートがインスタンス化し安いことを確認するのは簡単でしょう。それとも、私はまだ何かを見逃しているのだろうか?
きっと、(Jon Skeetが示唆したように)これは.NET 1.xのアドバイスに過ぎず、2005年にそうなるべきだったのに、まだ滅びていないのでしょうか。
解決するには?
最初のほうで言っている最適化をJITが行うことは、条件的に許されないんだ。ちょっと前に妖怪として話題になりましたが、有効ではありませんね。(しばらく前にJoe DuffyかVance Morrisonのどちらかに確認しましたが、どちらか覚えていません)。
volatile修飾子がなければ、取られたローカルコピーが古くなる可能性はありますが、それだけです。この場合
NullReferenceException
.
そして、確かにレースコンディションは存在します。しかし、常に存在するものなのです。仮に、このコードを次のように変更したとしましょう。
TheEvent(this, EventArgs.Empty);
ここで、そのデリゲートの呼び出しリストが1000件あるとします。他のスレッドがリストの末尾にあるハンドラの登録を解除する前に、リストの先頭にあるアクションが実行されることは完全にあり得ます。しかし、そのハンドラはまだ新しいリストなので、実行されます。(Delegateは不変です。) 私が見る限り、これは避けられないことです。
空のデリゲートを使うことで、確かにNullityチェックは回避できますが、レースコンディションを解決することはできません。また、常に変数の最新の値を見ることができるわけではありません。
関連
-
[解決済み】コンパイルエラー「未割り当てのローカル変数を使用しています」が発生したのはなぜですか?
-
[解決済み】クロススレッド操作が有効でない。作成されたスレッド以外のスレッドからアクセスされたコントロール
-
[解決済み】Android "ビュー階層を作成した元のスレッドだけが、そのビューに触れることができる"
-
[解決済み] 他のスレッドからGUIを更新するにはどうすればよいですか?
-
[解決済み] C#のStringとstringの違いは何ですか?
-
[解決済み] Javaにおける "implements Runnable "と "extends Thread "の違いについて
-
[解決済み] C++11では、標準化されたメモリモデルが導入されました。その意味するところは?そして、C++プログラミングにどのような影響を与えるのでしょうか?
-
[解決済み] プロセスとスレッドの違いは何ですか?
-
[解決済み] jQuery 複数のイベントで同じ関数を起動する
-
[解決済み] 非同期Task<T>メソッドを同期的に実行するにはどうしたらいいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】エラー。「戻り値を変更できません」 C#
-
[解決済み] [Entity Framework 4.1でエンティティに関連オブジェクトを追加する際に、エンティティオブジェクトをIEntityChangeTracker.の複数のインスタンスから参照できない。
-
[解決済み】ASP.NET Core Dependency Injectionのエラーです。アクティブ化しようとしているときに、タイプのサービスを解決できません。
-
[解決済み】WPFでXamlファイルにコメントを追加する方法は?
-
[解決済み] [Solved] 不正な文字列値: '\xEFxBFxBD' for column
-
[解決済み] UnityでOnCollisionEnterが呼ばれない
-
[解決済み】Swashbuckle/Swagger + ASP.Net Core: "Failed to load API definition" (API定義の読み込みに失敗しました
-
[解決済み] EntityTypeにキーが定義されていないエラー
-
[解決済み】OnCollisionEnter2Dが実行されない?
-
[解決済み】aspNetCore 2.2.0 - AspNetCoreModuleV2 エラー