技术开发 频道

好玩的C#基础:如何安全有效的引发事件?

  【IT168技术】最近在网上看到一篇很好的文章, 讨论如何安全有效的引发事件.也许你不一定要用到下面相同的解决方案, 但是至少你应该知道在引发事件时候需要考虑的问题.

  引发事件的问题

  引发事件是一个非常容易的事情, 但是的确也有它的误区. 让我们举个例子. 假设我们写个消息接收器, 每当我们收到一个新消息, 我们引发一个包含了新消息的事件 MessageReceived.

  安装我们通常的方法,就是:

public class MessageReceivedEventArgs : EventArgs{
    
// 接收到的消息
    public string Message { get; private set;
}
    
// 架构 ReceivedEventArgs
     public MessageReceivedEventArgs(string message)
    {
        Message
= message;
    }
}

   接下来, 我们创建一个非线程安全访问的类UnsafeMessenger来实现这个消息同时通知所有的订阅者(subscriber).

public class UnsafeMessenger{
    
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
    
// 当收到新消息时调用    public void OnNewMessage(string message)
    {
        
if (MessageReceived != null)        {
            MessageReceived(
this, new MessageReceivedEventArgs(message));
        }
    }
}

   注意, 通常OnNewMessage() 是私有的, 但是在这里为了测试的方便,我们将它设为public.

  大功告成!! 是吗? 事实上, 如果我们是单线程的程序, 这的确已经足够, 但是这是非线程安全访问(thread-safe).

  为什么? 想想, 订阅者可以任何时候订阅或者取消订阅. 比如,我们当前有一个订阅者, 那么当接收到一个新消息,执行到这一句时:

if (MessageReceived != null)

   肯定会通过, 因为有一个订阅者, 如果这个时候, 这名订阅者执行了取消订阅的命令:

myMessenger.MessageReceived -= MyMessageHandler;

   那么MessageReceived委托 就为null 了,

//已经通过了这个IF语句
if (MessageReceived != null){
    
//MessageReceived委托 就为null 了, 但是我们将要执行这句
    MessageReceived(this, new MessageReceivedEventArgs(message));
}

   这个时候, 就会引发NullReferenceException.

  方案一: 锁住它, 锁机制

        当允许多线程的时候, 我们可以用锁机制来避免一个用户在我们执行事件时订阅或者取消订阅, 或者在用户执行操作时, 不能引发事件

public class SyncronizedMessenger : IMessenger
{
   
// 委托和锁
    private EventHandler<MessageReceivedEventArgs> _messageReceived;
   
private readonly object _raiseLock = new object();
   
// 订阅/取消订阅的锁机制
    public event EventHandler<MessageReceivedEventArgs> MessageReceived
    {
        add {
lock (_raiseLock) { _messageReceived += value; } }
        remove {
lock (_raiseLock) { _messageReceived -= value; } }
    }
   
// 引发事件的锁机制
    public void OnNewMessage(string message)
    {
       
lock (_raiseLock)
        {
           
if (_messageReceived != null)
            {
                _messageReceived(
this, new MessageReceivedEventArgs(message));
            }
        }
    }
}

   这样, 如果有人试图订阅或取消订阅时, 必须要等待OnNewMessage事件的完成, 反之亦然.

0
相关文章