9

イベントをトリガーするときに(マルチスレッドアプリで)競合状態を回避するための一般的な方法は次のとおりです。

EventHandler<EventArgs> temp = SomeEvent;
if (temp != null) temp(e);

"Remember that delegates are immutable and this is why this technique works in theory. However, what a lot of developers don't realize is that this code could be optimized by the compiler to remove the local temp variable entirely. If this happens, this version of the code is identical to the first version, so a NullReferenceException is still possible."

問題(本によると)は、「このコードは、ローカルの一時変数を完全に削除するためにコンパイラーによって最適化される可能性があります。これが発生した場合、このバージョンのコードは最初のバージョンと同一であるため、NullReferenceExceptionが引き続き発生する可能性があります」

C#経由のCLRによると、これはコンパイラにイベントポインタをコピーさせるためのより良い方法です。

virtual void OnNewMail(NewMailEventArgs e)
{
    EventHandler<NewMailEventArgs> temp =
                          Interlocked.CompareExchange(ref NewMail, null, null);
    if (temp != null) 
        temp(this, e);
}

ここで、CompareExchangeは、NewMail参照がnullの場合はnullに変更し、nullでない場合はNewMailを変更しません。つまり、CompareExchangeはNewMailの値をまったく変更しませんが、NewMail内の値をアトミックでスレッドセーフな方法で返します。リヒター、ジェフリー(2010-02-12)。C#経由のCLR(p.265)。OReillyMedia-A.Kindle版。

私は.Net4.0フレームワークを使用していますが、Interlocked.CompareExchangeはイベントへの参照ではなく、場所への参照を想定しているため、これがどのように機能するかわかりません。

本に誤りがあるか、私がそれを誤解したかのどちらかです。誰かがこのメソッドを実装しましたか?または、ここで競合状態を防ぐためのより良い方法がありますか?

アップデート

それは私の間違いでした、反復ロックされたコードは機能します。間違ったキャストを指定しただけですが、Bradley(下記)によると、.net2.0以降のWindowsでは必要ありません。

4

4 に答える 4

8

コンパイラ(またはJIT)は、それを最適化することはできませんif/temp(CLR 2.0以降)。CLR 2.0メモリモデルでは、ヒープからの読み取りを導入できません(ルール#2)。

したがって、MyEvent2回目に読み取ることはできません。の値はステートメントtempで読み取る必要があります。if

この状況の詳細な説明と、標準パターンが適切である理由の説明については、私のブログ投稿を参照してください。

ただし、CLR 2.0メモリモデルの保証を提供しない(ただし、ECMAメモリモデルにのみ準拠する)Microsoft以外のCLR(モノラルなど)で実行している場合、またはItanium(悪名高い弱いハードウェアメモリモデル)、潜在的な競合状態を排除するために、Richterのようなコードが必要になります。

に関する質問に関してはInterlocked.CompareExchange、構文は、型のプライベートフィールドと、メソッドを持つパブリックイベントをpublic event EventHandler<NewMailEventArgs> NewMail宣言するためのC#構文糖衣です。呼び出しはプライベートフィールドの値を読み取るため、このコードはコンパイルされ、Richterが説明するように機能します。MicrosoftCLRでは不要です。EventHandler<NewMailEventArgs>addremoveInterlocked.CompareExchangeEventHandler<NewMailEventArgs>

于 2012-06-22T15:24:08.243 に答える
4

Interlocked.CompareExchangeの使用についてコメントすることはできないため、これはあなたの質問に対する部分的な回答にすぎませんが、この情報は役立つと思います。

問題は、コンパイラがif/tempを最適化する可能性があることです。

ええと、 C#経由のCLRによると(p。264–265)

[]コードは、ローカル[…]変数を完全に削除するようにコンパイラーによって最適化できます。この場合、このバージョンのコードは[イベントを2回参照するバージョン]と同じであるため、NullReferenceExceptionが発生する可能性があります。

したがって、 Microsoftのジャストインタイム(JIT)コンパイラがローカル変数を最適化しないことを知っておくことが重要ですこれは変更される可能性がありますが、多くのアプリケーションが破損する可能性があるため、ほとんどありません。

これは、.Netに強力なメモリモデルがあるためです:http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S5

読み取りと書き込みを導入することはできません。

ただし、このモデルでは読み取りを導入できません。これは、メモリから値を再フェッチすることを意味し、ローロックコードではメモリが変更される可能性があるためです。

 

ただし、はるかに弱いメモリモデルに従うMonoは、そのローカル変数を最適化することができます。

結論:Monoの使用を計画しているのでない限り、心配する必要はありません。

そしてそれでも、その振る舞いは揮発性の宣言で抑制できます。

于 2012-06-22T15:43:55.923 に答える
1

私はあなたが解釈を逃していると思います。場所とは、オブジェクト参照への単なるポインタを意味します[ msdn version:comparandと比較され、場合によっては置き換えられる宛先オブジェクト。]。次のコードは.NEt4.0で正常に機能します

public class publisher
{

    public event EventHandler<EventArgs> TestEvent;
    protected virtual void OnTestEvent(EventArgs e)
    {
        EventHandler<EventArgs> temp = Interlocked.CompareExchange(ref TestEvent, null, null);
        if (temp != null)
            temp(this,e);
    }
}
于 2012-06-22T15:39:20.263 に答える
0

生成されたILを見ると、このメソッドが次のように呼び出されていることがわかります。

IL_000d:  ldsflda    class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs> ConsoleApplication1.Program::MyEvent
IL_0012:  ldnull
IL_0013:  ldnull
IL_0014:  call       !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs>>(!!0&,!!0,!!0)

参照してくださいldsflda-私のイベントは静的ですが、フィールドのアドレスをロードしています。そのフィールドは、コンパイラーがイベントごとに生成する自動生成されたデリゲートフィールドです。

フィールドは次のように定義されます。

.field private static class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs> MyEvent
于 2012-06-22T15:45:20.457 に答える