.NET基础扩展系列-事件的实现原理

CLR VIA C#这本书中, 写到了事件的实现原理, CLR是使用委托字段来实现事件的:

复制代码
// 1. A PRIVATE delegate field that is initialized to null 
static EventHandler<EventArgs> NewMail = null;

// 2. A PUBLIC add_Xxx method (where Xxx is the Event name) 
// Allows methods to register interest in the event. 
static void add_NewMail(EventHandler<EventArgs> value)
{
    // The loop and the call to CompareExchange is all just a fancy way 
    // of adding a delegate to the event in a thread-safe way 
    EventHandler<EventArgs> prevHandler;
    EventHandler<EventArgs> tmp = NewMail;
    do
    {
        prevHandler = tmp;
        EventHandler<EventArgs> newHandler = (EventHandler<EventArgs>)Delegate.Combine(prevHandler, value); 
        tmp = Interlocked.CompareExchange<EventHandler<EventArgs>>(ref NewMail, newHandler, prevHandler);
    } while (tmp != prevHandler);
}


// 3. A PUBLIC remove_Xxx method (where Xxx is the Event name) 
// Allows methods to unregister interest in the event. 
public void remove_NewMail(EventHandler<EventArgs> value)
{
    // The loop and the call to CompareExchange is all just a fancy way 
    // of removing a delegate from the event in a thread-safe way 
    EventHandler<EventArgs> prevHandler; 
    EventHandler<EventArgs> tmp = NewMail;
    do
    {
        prevHandler = tmp;
        EventHandler<EventArgs> newHandler = (EventHandler<EventArgs>)Delegate.Remove(prevHandler, value);
        tmp= Interlocked.CompareExchange<EventHandler<EventArgs>>(ref NewMail, newHandler, prevHandler);
    } while (tmp!= prevHandler);
}
复制代码

 

但是里面tmp = Interlocked.CompareExchange<EventHandler<EventArgs>>(ref NewMail, newHandler, prevHandler);

这一句是用于赋值的, 为什么不采用Interlocked.Exchange方法直接赋值呢? 而要采取这么迂回的方式, 使用CompareExchange和(newMail != prevHandler)来组合判断. 

 

在单线程的情况下, 这两种情况的效果是一样的. 

而在用多线程并发的情况下, 就会发现为什么只能用CompareExchange.

 

假设有两个线程A和B, 同时在添加实现响应函数, 也就是调用add_NewMail函数. 

 

假设事件NewMail初始化时没有时间响应函数, A需要添加一个FunctionA响应函数, B需要添加一个FunctionB函数.

 

第一步:线程A先开始执行,执行完下面这一句后, 停止执行:EventHandler<EventArgs> newHandler =(EventHandler<EventArgs>)Delegate.Combine(prevHandler, value); 

  此时的NewMail 为null, newHandler 为FunctionA. 线程A停止.

 

第二步:线程B开始执行,将整个函数执行完, 

  此时NewMail 为FunctionB

 

第三步:线程A继续执行, 会执行这一句: tmp = Interlocked.CompareExchange<EventHandler<EventArgs>>(ref NewMail, newHandler, prevHandler);

由于此时NewMail =FunctionB , 所以tmp 会=FunctionB ,

但由于prevHandler= FunctionA, 所以NewMail!=prevHandler, 所以NewMail保持值不变. 

往下执行while (tmp != prevHandler)的判断, 就会发现tmp!=prevHandler, 就会继续循环, 直到NewMail 和prevHandler 值一样了,才会退出循环.

这样看就比较清楚, 为什么不能用Exchange了, 如果使用Exchange,那么在第三步的时候, 就会直接执行赋值, 将NewMail设置为FunctionA,那么FucntionB就不会出现在时间相应函数的委托链表中.

 

为什么CompareExchange, 就可以呢?

其实是用CompareExchange去验证线程并发控制的一个最根本的原则: 如果一个操作要被看做是不可打断的, 那么从开始到完成的真个环节中, 操作的环境不能改变. 

我们随使用CompareExchange, 就可以验证NewMail 是不是我们最开始操作时候的prevHandler, 如果不是, 说明操作的基本环境已经改变, 这次操作就是无效的. 

这种方式, 可以在不适用lock的情况下, 实现并发控制, 相当精妙. 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值