web-dev-qa-db-ja.com

イベントをディスパッチする前にnullをチェックしています...スレッドセーフですか?

私を混乱させるものですが、問題を引き起こしたことはありません...イベントをディスパッチするための推奨される方法は次のとおりです:

public event EventHandler SomeEvent;
...
{
    ....
    if(SomeEvent!=null)SomeEvent();
}

マルチスレッド環境で、このコードは、別のスレッドがnullのチェックとイベントの呼び出しの間でSomeEventの呼び出しリストを変更しないことをどのように保証しますか?

35
spender

C#6.0では、モナディックNull条件演算子?.を使用してnullを確認し、簡単でスレッドセーフな方法でイベントを発生させることができます。

SomeEvent?.Invoke(this, args);

左側を1回だけ評価し、それを一時変数に保持するため、スレッドセーフです。あなたはもっと読むことができます ここ 部分的にNull条件演算子と題されています。

24

ご指摘のとおり、複数のスレッドがSomeEventに同時にアクセスできる場合、1つのスレッドがSomeEventがnullであるかどうかを確認し、nullでないと判断できます。その直後に、別のスレッドが最後に登録されたデリゲートをSomeEventから削除する可能性があります。最初のスレッドがSomeEventを発生させようとすると、例外がスローされます。このシナリオを回避する合理的な方法は次のとおりです。

protected virtual void OnSomeEvent(EventArgs args) 
{
    EventHandler ev = SomeEvent;
    if (ev != null) ev(this, args);
}

これが機能するのは、addおよびremoveアクセサーのデフォルト実装を使用してデリゲートがイベントに追加またはイベントから削除されるたびに、Delegate.CombineおよびDelegate.Remove静的メソッドが使用されるためです。これらの各メソッドは、渡されたインスタンスを変更するのではなく、デリゲートの新しいインスタンスを返します。

さらに、.NETでのオブジェクト参照の割り当ては atomic であり、追加および削除イベントアクセサーのデフォルトの実装は synchronized です。したがって、上記のコードは、最初にマルチキャストデリゲートをイベントから一時変数にコピーすることで成功します。この時点以降にSomeEventを変更しても、作成して保存したコピーには影響しません。したがって、デリゲートが登録されているかどうかを安全にテストし、その後で呼び出すことができます。

このソリューションは、1つの競合問題、つまり、呼び出されたときにイベントハンドラーがnullになるという問題を解決することに注意してください。イベントハンドラーが呼び出されたときにイベントハンドラーが無効になる問題、またはコピーが取得された後にイベントハンドラーがサブスクライブする問題は処理しません。

たとえば、イベントハンドラーがサブスクライブ解除されるとすぐに破棄される状態に依存している場合、このソリューションは適切に実行できないコードを呼び出す可能性があります。詳細は Eric Lippertの優れたブログエントリ を参照してください。また、 このStackOverflowの質問と回答 も参照してください。

編集:C#6.0を使用している場合、 Krzysztofの答え は良い方法のように見えます。

55
HTTP 410

このnullチェックを削除する最も簡単な方法は、イベントハンドラーを匿名のデリゲートに割り当てることです。ペナルティはほとんど発生せず、すべてのnullチェック、競合状態などから解放されます。

public event EventHandler SomeEvent = delegate {};

関連質問: イベント宣言に匿名の空のデリゲートを追加することの欠点はありますか?

22
Cherian

推奨される方法は少し異なり、次のように一時的に使用します。

EventHandler tmpEvent = SomeEvent;
if (tmpEvent != null)
{
    tmpEvent();
}
4
denis phillips

EventHandlerの拡張関数を使用して、RoadWarriorの回答を少し改善することを提案します。

public static class Extensions
{
    public static void Raise(this EventHandler e, object sender, EventArgs args = null)
    {
        var e1 = e;

        if (e1 != null)
        {
            if (args == null)
                args = new EventArgs();

            e1(sender, args);
        }                
    }
  }

この拡張機能を使用すると、次の方法でイベントを発生させることができます。

クラスSomeClass {パブリックイベントEventHandler MyEvent;

void SomeFunction()
{
    // code ...

    //---------------------------
    MyEvent.Raise(this);
    //---------------------------
}

}

c#イベント

0
Gidi Baum