2012-04-17 25 views
2

非同期コールバックのマルチスレッド環境で、イベントハンドラを適切に追加/削除する方法について質問があります。マルチスレッドアプリケーションのイベントハンドラの追加と削除

私は、非管理コードからコールバックを送出するProxyDLLからの非同期コールバックを受け取るMyCoreクラスを持っています。私はイベントを購読するフォームを持っています。

イベントの着脱にはどのようなアプローチが適していますか。私はMulticastDelegateが_invocationcountを持っていることに気づいた。それは何ですか?コールバックが完了するまでコールバックコールが処理中の場合、イベントの内部ロジックはイベントから切り離されますか?その子犬のために_invocationcountが存在しますか? イベントからの脱落は(一般的に)トレッドセーフですか?

class Form1 
{ 
    EventHandler m_OnResponse; 
    Int32 m_SomeValue; 
    Form1() 
    { 
    m_OnResponse = new EventHandler(OnResponseImpl); 
    m_MyCore.SetCallBackOnLogOn(m_OnResponse); 
    } 
    ~Form1() 
    { 
    m_MyCore.ReleaseCallBackOnLogOn(m_OnResponse); 
    } 
    private OnResponseImpl(object sender, EventArgs e) 
    { 
    Thread.Sleep(60*1000); 

    m_SomeValue = 1;    // <<-- How to/Who guarantees that Form1 obj is still 
           // alive. May be callback was invoked earlier and 
           // we just slept too long 

    if (!this.IsDisposed) 
    { 
     invokeOnFormThread(DoOnResponseImpl, sender, e); 
    } 
    } 
} 

class MyCore 
{ 
    private event EventHandler OnLogOn; 
    public void SetCallBackOnLogOn(EventHandler fn) 
    { 
    // lock (OnLogOn) 
    { 
     OnLogOn += fn; 
    } 
    } 
    ReleaseCallBackOnLogOn(EventHandler fn) 
    { 
    // lock (OnLogOn) 
    { 
     OnLogOn -= fn; 
    } 
    } 
    public void DoDispatchOnLogOn() 
    { 
    // lock (OnLogOn) 
    { 
     if (OnLogOn != null) 
     { 
     OnLogOn(this, null); 
     } 
    } 
    } 
} 

答えて

3

デフォルトのイベントの追加および削除操作はalready thread-safeです。ほとんどの場合、その部分について心配する必要はありません。これは、あなたが心配する必要があるマルチキャストデリゲートの呼び出しです。私はここにいた何

public void DoDispatchOnLogOn() 
{ 
    EventHander local; 
    lock (this) 
    { 
    local = OnLogOn; 
    } 
    if (local != null) 
    { 
    local(this, null); 
    } 
} 

OnLogOnデリゲートのチェーンを保持するローカル変数を作成することでした。ここではマルチキャスト代理人の不変性を利用して、ヌルと呼び出しシーケンスのスレッドセーフなチェックを行うことができます。 lockは、OnLogonの「新鮮な」読み取りを保証するためにのみ使用され、「古い」読み取りを取得しても構わない場合は厳密にオプションです。

更新:

私は ReleaseCallBackOnLogOnが退会デリゲートを終了したときにコールバックが呼び出されていないことを確認する必要があります。

ほとんどの場合、私が持っているコードは、登録解除されたイベントハンドラを実行しようとはしません。イベントハンドラを削除してから、イベントハンドラが最近削除されたにもかかわらず実行される可能性のあるイベントを発生させるまでには若干の競争があります。

コールバックが完全に完了するまで、私のクラスインスタンスがまだ生存していることを確認する必要があります。

代理人は、ターゲットメソッドを含むクラスインスタンスへの参照を保持します。これにより、インスタンスがルートされ、ガーベッジ・コレクションの対象にならなくなります。あなたはこれについて心配する必要はありません。

~Form1ファイナライザは、イベントハンドラを削除するのには適していないことを指摘しておきます。デリゲートは、ターゲットメソッドを含むインスタンスへの参照を保持するので、ほとんどの場合、ファイナライザは呼び出されず、イベントハンドラはイベントから削除されません。

+0

ありがとうございます!それは良い点です。そして、安全性に関して - イベントからのリスナーの登録を解除することがブロッキングコールであると言いたいのですか? – adspx5

+0

@ adspx5:C#4.0以上では、Interlocked.CompareExchangeを使用してサブスクライブとサブスクライブを行います。 C#3.0以下では 'lock(this)'を使用します。したがって、現在ロックが他の誰かによって保持されている場合は、C#3.0以下がブロックされます。 –

+0

それは私が理解しようとしているものではありません。私は理解しましたが、呼び出しリストの変更については心配しないでください。私は代議員の生涯について心配しています。 ReleaseCallBackOnLogOnがサブスクライブ解除のデリゲートを終了したときにコールバックが呼び出されないようにする必要があります。私の代理人が10秒間スリープ状態になり、その後、MyForm :: m_Somevalueを変更したとします。コールバックの呼び出しが完全に完了するまで、クラスインスタンスがまだ生存していることを確認する必要があります。 – adspx5

関連する問題