可以安全地在多线程应用程序中使用事件

时间:2012-07-25 22:23:33

标签: c# multithreading events

在下面的代码中,我有两个类,一个在一个单独的线程中运行并触发事件,另一个订阅此事件并从事件中接收数据。我的事件代码基于Jon Skeet的文章http://csharpindepth.com/Articles/Chapter2/Events.aspx

在本文http://www.codeproject.com/Articles/37474/Threadsafe-Events中,它说......

  

出于这个原因,我推荐Jon Skeet最后在代表和事件结束时推荐的相同方法:“不要这样做”,即不要以多线程方式使用事件。如果对象上存在事件,那么只有一个线程应该能够订阅或取消订阅该事件,并且它是引发事件的相同线程。

现在显然我的设计打破了这一点,因为它在与订阅的不同线程上触发事件。我怎么能修改我的设计,以便它遵循不以多线程方式使用事件的原则,或者这是不可能的?

我想做的另一种方法就是将我的回调方法作为委托传递到B类并调用它而不是调用事件?

我可能完全没错,所以任何澄清都会受到赞赏。

注意:我知道.Net 4.0显然已经解决了这个问题,但是如果有办法在.Net 4之前做到这一点我还是会感兴趣

public delegate void MyDelegate(int a);

class A
{
    void main()
    {
        B bObject = new B();
        bObject.MyEvent += new MyDelegate(NiceMethod);
        bObject.Run();
    }   

    void NiceMethod(int a)
    {   
        Console.Writeline({0}, a);  
    }
}

class B
{
    readonly object eventLock = new object();

    MyDelegate myDel;

    public event MyDelegate MyEvent
    {   
        add
        {
            lock (eventLock)
            {
                myDel += value;
            }
        }
        remove
        {
            lock (eventLock)
            {
                myDel -= value;
            }
        }
    }   

    //Assume this runs in a new thread and calls back data using MyEvent
    //Have ommited thread code for simplicity
    public void Run()
    {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(1000);
            MyDelegate handler;
            lock (someEventLock)
            {
                handler = myDel;
            }
            if (handler != null)
            {
                handler (i);
            }
        }   
    }
}

1 个答案:

答案 0 :(得分:1)

从不同的线程中引发事件或侦听事件没有任何问题。监听器负责处理从另一个线程调用的问题。正如Marc Gravell在他的评论中指出的那样,在编译器生成的addremove实现中,(并且始终)支持在不同线程的事件中添加和删除侦听器。唯一的问题是以线程安全的方式引发事件,这可以通过使用生成的addremove正在使用的相同类型的自旋锁同步对事件的访问来完成:

class B 
{  
    public event MyDelegate MyEvent;

    protected OnMyEvent(int p_Arg)
    {
        // Delegates are immutable and add/remove default
        // implementations always generate a new instance of the 
        // delegate. Therefore, tTmp (if not null) can be safely invoked
        var tTmp = 
            System.Threading.Interlocked
            .CompareExchange(ref MyEvent, null, null);
        if (tTmp != null) {
            tTmp(p_Arg);
        }
    }

    //Assume this runs in a new thread and calls back data using MyEvent 
    //Have ommited thread code for simplicity 
    public void Run() 
    { 
        for (int i = 0; i < 100; i++) 
        { 
            OnMyEvent(i);
        }    
    } 
} 

唯一可能发生的事情就是在之后调用从事件列表中删除侦听器。恕我直言,听众必须能够处理这种情况,因为它处理从单独的线程调用的beeing ...