在 ILSpy、Google 和一两句脏话的帮助下,我能够准确地重现方法Add
和Remove
标准的实现event
。
结果,事件代码看起来是这样的,无论如何,在Release编译之后,IL代码是100%等价的。在 Debug 中,它自然有所不同,但只是略有不同,即在检查循环条件方面。
//field-like объявление события
public event Action SampleEvent1;
//итоги декомпиляции
private Action eventDelegate;
public event Action SampleEvent2
{
add
{
Action current = eventDelegate,
Action comparer;
do
{
comparer = current;
Action combine = comparer + value;
current = Interlocked.CompareExchange(ref eventDelegate, combine, comparer);
}
while (!object.ReferenceEquals(current, comparer));
}
remove
{
Action current = eventDelegate,
Action comparer;
do
{
comparer = current;
Action combine = comparer - value;
current = Interlocked.CompareExchange(ref eventDelegate, combine, comparer);
}
while (!object.ReferenceEquals(current, comparer));
}
}
现在给鉴赏家一个问题:我知道所有这些都是支持多线程所必需的,并且使用lock
会导致死锁,但我不完全理解这个选项是如何工作的,最重要的是,为什么。
看。
订阅/取消订阅事件应该是原子的,这样就不可能有人订阅了而事件没有到达。旧版本确实阻止了:
这种方式的缺点是锁需要对象,拿哪个对象呢?你可以采用一个“不可见”的对象,但是这个对象必须以某种方式在标准中指定并可供使用(例如,如果我们想在同一个锁下读取委托值),这不太好,因为它规定了实施细节。因此,它被使用
this
。但
this
反过来又导致了另一个问题:它可以被任何人从外面阻挡!因此,决定放弃这个想法,转而采用非阻塞(无锁)算法,这种算法不需要锁定对象,而且速度更快、效率更高。这个怎么运作?这就是如何。稍微重命名变量:
看看它做
Interlocked.CompareExchange
了什么,为了清楚起见,可以这样重写:发生了什么?在
current
循环迭代开始时,会有一个值eventDelegate
。我们将它存储在一个临时变量中noncombined
并将value
其添加以获取委托combined
。现在我们正在尝试将结果写回。如果此时没有人设法从另一个线程更改我们的委托(很可能会),那么它将Interlocked.CompareExchange
成功完成,将委托写入到位,并且委托的current
旧值将在其中。这将结束循环,检查current != noncombined
将给出false
.如果在我们尝试合并时,另一个线程发生了变化
eventDelegate
,则条件测试Interlocked.CompareExchange
将失败。在这种情况下,eventDelegate
什么都不能写入,因为我们将丢失更改后的值!然后我们只需将这个新值写入current
并转到下一次迭代(测试current != noncombined
将给出true
)。在下一次迭代中,我们将做同样的事情:eventDelegate
我们将尝试将一个新委托与当前值结合起来,并将其写入到位,同时检查eventDelegate
在此期间是否没有人再次更改。这应该是一种典型的非阻塞技术,我在非阻塞算法中看到了很多类似的代码。