C#事件和线程安全 [英] C# Events and Thread Safety

查看:118
本文介绍了C#事件和线程安全的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常听到/读以下意见:

始终进行事件的副本,你检查它的之前启动它。这将消除一个潜在的问题与线程该事件成为在正确的,你检查空和你在哪里触发事件之间的位置:

  //检查前的事件委托复制/电话
事件处理程序的副本= TheEvent;如果(副本!= NULL)
    复制(这一点,EventArgs.Empty); //调用任何处理程序复制列表上

更新的:我从阅读的优化,这也可能需要事件成员挥发想过,但他的回答乔恩斯基特规定,CLR不优化掉副本

但与此同时,为了甚至出现此问题,另一个线程一定做了这样的事情:

  //从事件更好的退市 - 不希望我们的处理从现在开始叫:
otherObject.TheEvent - = OnTheEvent;
//好,现在我们可以肯定的是OnTheEvent将无法运行...

实际的顺序可能是这种混合物:

  //检查前的事件委托复制/电话
事件处理程序的副本= TheEvent;//更好地退市事件 - 不想从现在起叫我们的处理:
otherObject.TheEvent - = OnTheEvent;
//好,现在我们可以肯定的是OnTheEvent将无法运行...如果(副本!= NULL)
    复制(这一点,EventArgs.Empty); //调用任何处理程序复制列表上

问题的关键在于 OnTheEvent 运行,笔者有退订后,但他们只是退订明确,以避免这种情况发生。当然,真正需要的是适当的同步自定义事件实施的添加删除访问。而在另外如果在事件被触发锁被关押在那里可能死锁的问题。

原来是这样货物崇拜编程?看起来是这样 - 很多人必须采取这一步骤来保护他们的code多线程,而实际上在我看来,事件需要比这更多的照顾,他们可以作为一个多部分之前-threaded设计。因此,谁没有采取额外照顾的人可能也忽略了这个建议 - 它根本不是单线程程序的问题,而事实上,由于缺乏挥发性在大多数在线例如code,建议可以是具有完全没有效果。

(而且是不是简单了很多,只是指定空的委托{} 有关成员声明,​​使你永远需要检查摆在首位?)

更新:的如果它是不明确的,我没有把握建议的意图 - 避免在任何情况下一个空引用异常。我的观点是,这个特殊的空引用异常只能出现如果另一个线程从事件退市,而这样做的唯一原因是为了确保没有进一步的调用将通过该事件,这显然不是通过这种技术实现的接收。你会隐瞒的竞争条件 - 这将是更好地揭示了!那空例外有助于检测组件的滥用。如果你希望你的组件免受虐待,你可以按照WPF的例子 - 线程ID存储在你的构造函数,如果另一个线程试图直接与你的组件进行交互,然后抛出一个异常。要不然实现真正的线程安全的组件(不是一件容易的任务)。

所以我主张,仅仅这样做拷贝/检查成语是货物崇拜编程,增加混乱和噪音的code。要真正防止其他线程需要大量更多的工作。

更新响应埃里克利珀的博客文章:

所以这是我已经错过了有关事件处理的主要事情:事件处理程序都必须在被称为甚至在活动结束后已取消订阅面对强大的,显然,因此我们只需要关心的可能性事件委托是的。的是任何地方记录在事件处理程序的要求?

和如此:还有其他的方法来解决这个问题;例如,初始化处理程序有一个永远不会被删除空操作,但做一个空检查是标准模式

所以,剩下的我的问题的片段之一,的为什么是明确的空检查标准模式?的另一种选择,分配空的委托,只需要 =代表{} 添加到事件的声明,这消除了该事件引发的每一个地方臭仪式的那些小堆。这将是很容易,以确保空的代表是便宜的实例。还是我还是失去了一些东西?

当然,它必须是(作为乔恩斯基特建议)这是不是死了,因为它应该在2005年所做的只是.NET 1.x的意见?


解决方案

JIT不在允许执行的,因为条件你谈论在第一部分的优化。我知道这是在不久前提出一个幽灵,但它是无效的。 (我不是乔·达菲或万斯莫里森检查了它前一段时间,我不记得)

如果没有volatile修饰符这是可能的,所采取的本地副本会过时的,但仅此而已。它不会引起的NullReferenceException

是的,肯定是一个竞争条件 - 但总是会。假设我们只是改变了code为:

  TheEvent(这一点,EventArgs.Empty);

现在假设该委托调用列表中有1000个条目。这是完全可能的,另一个线程退订邻近列表的末尾的处理程序之前,在列表的开头的动作将已经执行。然而,处理器仍然会被执行,因为这将是一个新的列表。 (代表是不可改变的。)据我可以看到这是不可避免的。

用空的委托肯定避免无效检查,但不能解决竞争状态。它也并不能保证你总是看到变量的最新值。

I frequently hear/read the following advice:

Always make a copy of an event before you check it for null and fire it. This will eliminate a potential problem with threading where the event becomes null at the location right between where you check for null and where you fire the event:

// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;

if (copy != null)
    copy(this, EventArgs.Empty); // Call any handlers on the copied list

Updated: I thought from reading about optimizations that this might also require the event member to be volatile, but Jon Skeet states in his answer that the CLR doesn't optimize away the copy.

But meanwhile, in order for this issue to even occur, another thread must have done something like this:

// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...

The actual sequence might be this mixture:

// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;

// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;    
// Good, now we can be certain that OnTheEvent will not run...

if (copy != null)
    copy(this, EventArgs.Empty); // Call any handlers on the copied list

The point being that OnTheEvent runs after the author has unsubscribed, and yet they just unsubscribed specifically to avoid that happening. Surely what is really needed is a custom event implementation with appropriate synchronisation in the add and remove accessors. And in addition there is the problem of possible deadlocks if a lock is held while an event is fired.

So is this Cargo Cult Programming? It seems that way - a lot of people must be taking this step to protect their code from multiple threads, when in reality it seems to me that events require much more care than this before they can be used as part of a multi-threaded design. Consequently, people who are not taking that additional care might as well ignore this advice - it simply isn't an issue for single-threaded programs, and in fact, given the absence of volatile in most online example code, the advice may be having no effect at all.

(And isn't it a lot simpler to just assign the empty delegate { } on the member declaration so that you never need to check for null in the first place?)

Updated: In case it wasn't clear, I did grasp the intention of the advice - to avoid a null reference exception under all circumstances. My point is that this particular null reference exception can only occur if another thread is delisting from the event, and the only reason for doing that is to ensure that no further calls will be received via that event, which clearly is NOT achieved by this technique. You'd be concealing a race condition - it would be better to reveal it! That null exception helps to detect an abuse of your component. If you want your component to be protected from abuse, you could follow the example of WPF - store the thread ID in your constructor and then throw an exception if another thread tries to interact directly with your component. Or else implement a truly thread-safe component (not an easy task).

So I contend that merely doing this copy/check idiom is cargo cult programming, adding mess and noise to your code. To actually protect against other threads requires a lot more work.

Update in response to Eric Lippert's blog posts:

So there's a major thing I'd missed about event handlers: "event handlers are required to be robust in the face of being called even after the event has been unsubscribed", and obviously therefore we only need to care about the possibility of the event delegate being null. Is that requirement on event handlers documented anywhere?

And so: "There are other ways to solve this problem; for example, initializing the handler to have an empty action that is never removed. But doing a null check is the standard pattern."

So the one remaining fragment of my question is, why is explicit-null-check the "standard pattern"? The alternative, assigning the empty delegate, requires only = delegate {} to be added to the event declaration, and this eliminates those little piles of stinky ceremony from every place where the event is raised. It would be easy to make sure that the empty delegate is cheap to instantiate. Or am I still missing something?

Surely it must be that (as Jon Skeet suggested) this is just .NET 1.x advice that hasn't died out, as it should have done in 2005?

解决方案

The JIT isn't allowed to perform the optimization you're talking about in the first part, because of the condition. I know this was raised as a spectre a while ago, but it's not valid. (I checked it with either Joe Duffy or Vance Morrison a while ago; I can't remember which.)

Without the volatile modifier it's possible that the local copy taken will be out of date, but that's all. It won't cause a NullReferenceException.

And yes, there's certainly a race condition - but there always will be. Suppose we just change the code to:

TheEvent(this, EventArgs.Empty);

Now suppose that the invocation list for that delegate has 1000 entries. It's perfectly possible that the action at the start of the list will have executed before another thread unsubscribes a handler near the end of the list. However, that handler will still be executed because it'll be a new list. (Delegates are immutable.) As far as I can see this is unavoidable.

Using an empty delegate certainly avoids the nullity check, but doesn't fix the race condition. It also doesn't guarantee that you always "see" the latest value of the variable.

这篇关于C#事件和线程安全的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆