2012-04-17 63 views
2

我有一個問題如何正確地在多線程環境中添加/刪除事件處理程序異步回調。添加和刪除事件處理程序爲多線程應用

我有MyCore類從ProxyDLL接收異步回調其從非託管代碼調度回調。我有表單(託管),其中訂閱了事件。

什麼將安裝/從事件剝離正確的方法。我注意到MulticastDelegate有_invocationcount。它能做什麼?如果回調調用正在進行,直到回調完成,事件的內部邏輯是否阻止與事件分離?該puprose是否存在_invocationcount? 從事件(一般來說)是detathing是踩踏?

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委託鏈。我在這裏利用多播委託的不變性,以便我們可以對null和調用序列進行線程安全檢查。該lock只是用來保證一個「鮮」讀OnLogon,它是,如果你不介意流汗一個「陳舊」讀可選的。

更新:

我需要確保當 ReleaseCallBackOnLogOn結束退訂委託回調,則不會調用。

大多數情況下,我擁有的代碼不會嘗試執行任何已取消訂閱的事件處理程序。在刪除事件處理程序和引發可能導致事件處理程序即使剛剛被刪除的事件處理程序被執行的事件之間也存在細微的競爭。

我需要確定我的類實例還活着,直到調用 回調完成。

委託持有對包含目標方法的類實例的引用。這將保持您的實例爲根,因此不符合垃圾回收的條件。你不需要擔心這一點。

我要指出的是,~Form1終結是不是一個好移除事件處理程序的地方。請記住,代表持有對包含目標方法的實例的引用,因此在大多數情況下,終結器不會被調用,並且事件處理程序不會從事件中刪除。

+0

謝謝!這是一個很好的觀點。關於安全性 - 你想說從退出事件中取消訂閱監聽器是一個阻塞呼叫嗎? – adspx5 2012-04-17 18:52:53

+0

@ adspx5:C#4.0和更高版本使用'Interlocked.CompareExchange'做註冊和取消。 C#3.0及更低版本使用'lock(this)'。因此,如果鎖定當前由其他人持有,C#3.0及更低版本將會阻止。 – 2012-04-17 19:39:59

+0

這不完全是我想了解的。我明白了,但不用擔心調用列表的變化。我擔心我的代表一生。我需要確保在ReleaseCallBackOnLogOn結束取消訂閱委託時不會調用回調。猜猜我的委託人睡了10秒,然後改變了MyForm :: m_Somevalue。我需要確保我的類實例仍然存在,直到完成調用回調。 – adspx5 2012-04-17 20:47:00