2012-04-17 61 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