C# 在事件调度之前检查空值...线程安全吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/282653/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Checking for null before event dispatching... thread safe?
提问by spender
Something that confuses me, but has never caused any problems... the recommended way to dispatch an event is as follows:
一些让我感到困惑但从未引起任何问题的东西......推荐的调度事件的方法如下:
public event EventHandler SomeEvent;
...
{
....
if(SomeEvent!=null)SomeEvent();
}
In a multi-threaded environment, how does this code guarantee that another thread will not alter the invocation list of SomeEvent
between the check for null and the invocation of the event?
在多线程环境中,这段代码如何保证另一个线程不会改变SomeEvent
检查空值和事件调用之间的调用列表?
采纳答案by Krzysztof Branicki
In C# 6.0 you can use monadic Null-conditional operator ?.
to check for null and raise events in easy and thread-safe way.
在 C# 6.0 中,您可以使用 monadic Null 条件运算符?.
来检查 null 并以简单且线程安全的方式引发事件。
SomeEvent?.Invoke(this, args);
It's thread-safe because it evaluates the left-hand side only once, and keeps it in a temporary variable. You can read more herein part titled Null-conditional operators.
它是线程安全的,因为它只计算左侧一次,并将其保存在一个临时变量中。您可以在此处阅读更多内容,部分标题为 Null 条件运算符。
回答by denis phillips
The recommended way is a little different and uses a temporary as follows:
推荐的方式有点不同,使用临时的如下:
EventHandler tmpEvent = SomeEvent;
if (tmpEvent != null)
{
tmpEvent();
}
回答by HTTP 410
As you point out, where multiple threads can access SomeEvent
simultaneously, one thread could check whether SomeEvent
is null and determine that it isn't. Just after doing so, another thread could remove the last registered delegate from SomeEvent
. When the first thread attempts to raise SomeEvent
, an exception will be thrown. A reasonable way to avoid this scenario is:
正如您所指出的,在多个线程可以SomeEvent
同时访问的情况下,一个线程可以检查是否SomeEvent
为 null 并确定它不是。这样做之后,另一个线程可以从SomeEvent
. 当第一个线程尝试 raise 时SomeEvent
,将抛出异常。避免这种情况的合理方法是:
protected virtual void OnSomeEvent(EventArgs args)
{
EventHandler ev = SomeEvent;
if (ev != null) ev(this, args);
}
This works because whenever a delegate is added to or removed from an event using the default implementations of the add and remove accessors, the Delegate.Combine and Delegate.Remove static methods are used. Each of these methods returns a new instance of a delegate, rather than modifying the one passed to it.
这是有效的,因为每当使用 add 和 remove 访问器的默认实现将委托添加到事件中或从事件中删除时,都会使用 Delegate.Combine 和 Delegate.Remove 静态方法。这些方法中的每一个都返回一个新的委托实例,而不是修改传递给它的实例。
In addition, assignment of an object reference in .NET is atomic, and the default implementations of the add and remove event accessors are synchronised. So the code above succeeds by first copying the multicast delegate from the event to a temporary variable. Any changes to SomeEvent after this point will not affect the copy you've made and stored. Thus you can now safely test whether any delegates were registered and subsequently invoke them.
此外,.NET 中对象引用的分配是原子的,并且 add 和 remove 事件访问器的默认实现是同步的。因此,上面的代码通过首先将多播委托从事件复制到临时变量来成功。在此之后对 SomeEvent 的任何更改都不会影响您制作和存储的副本。因此,您现在可以安全地测试是否有任何委托被注册并随后调用它们。
Note that this solution solves one race problem, namely that of an event handler being null when it's invoked. It doesn't handle the problem where an event handler is defunct when it's invoked, or an event handler subscribes after the copy is taken.
请注意,此解决方案解决了一个竞争问题,即事件处理程序在调用时为 null 的问题。它不处理事件处理程序在调用时失效的问题,或者事件处理程序在复制后订阅的问题。
For example, if an event handler depends on state that's destroyed as soon as the handler is un-subscribed, then this solution might invoke code that cannot run properly. See Eric Lippert's excellent blog entryfor more details. Also, see this StackOverflow question and answers.
例如,如果事件处理程序依赖于在处理程序取消订阅后立即销毁的状态,则此解决方案可能会调用无法正常运行的代码。有关更多详细信息,请参阅Eric Lippert 的优秀博客条目。另外,请参阅此 StackOverflow 问题和答案。
EDIT: If you're using C# 6.0, then Krzysztof's answerlooks like a good way to go.
编辑:如果您使用的是 C# 6.0,那么Krzysztof 的答案看起来是一个不错的方法。
回答by HTTP 410
Safer approach:
更安全的方法:
public class Test
{
private EventHandler myEvent;
private object eventLock = new object();
private void OnMyEvent()
{
EventHandler handler;
lock(this.eventLock)
{
handler = this.myEvent;
}
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
public event MyEvent
{
add
{
lock(this.eventLock)
{
this.myEvent += value;
}
}
remove
{
lock(this.eventLock)
{
this.myEvent -= value;
}
}
}
}
-bill
-账单
回答by Cherian
The simplest way remove this null check is to assign the eventhandler to an anonymous delegate. The penalty incurred in very little and relieves you of all the null checks, race conditions etc.
删除此空检查的最简单方法是将事件处理程序分配给匿名委托。产生的惩罚非常少,可以让您免于所有的空检查、竞争条件等。
public event EventHandler SomeEvent = delegate {};
public event EventHandler SomeEvent = delegate {};
Related question: Is there a downside to adding an anonymous empty delegate on event declaration?
相关问题:在事件声明中添加匿名空委托有什么缺点吗?
回答by Gidi Baum
I would like to suggest an slight improvment to RoadWarrior's answer by utilizing an extention function for the EventHandler:
我想建议通过使用 EventHandler 的扩展功能对 RoadWarrior 的答案稍作改进:
public static class Extensions
{
public static void Raise(this EventHandler e, object sender, EventArgs args = null)
{
var e1 = e;
if (e1 != null)
{
if (args == null)
args = new EventArgs();
e1(sender, args);
}
}
}
With this extension in scope, events can be raised simply by:
有了这个范围的扩展,事件可以简单地通过以下方式引发:
class SomeClass { public event EventHandler MyEvent;
class SomeClass { public event EventHandler MyEvent;
void SomeFunction()
{
// code ...
//---------------------------
MyEvent.Raise(this);
//---------------------------
}
}
}