Dan*_*ker 230 c# events multithreading
UPDATE
从C#6开始,这个问题的答案是:
SomeEvent?.Invoke(this, e);
Run Code Online (Sandbox Code Playgroud)
我经常听到/阅读以下建议:
在检查事件之前,请务必复制事件null并将其触发.这将消除线程的潜在问题,其中事件变为null位于您检查null和触发事件的位置之间的位置:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Run Code Online (Sandbox Code Playgroud)
更新:我从阅读中了解到这可能还需要事件成员的优化,但Jon Skeet在他的回答中指出CLR不会优化副本.
但同时,为了解决这个问题,另一个线程必须做到这样的事情:
// 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...
Run Code Online (Sandbox Code Playgroud)
实际的顺序可能是这种混合物:
// 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
Run Code Online (Sandbox Code Playgroud)
关键是OnTheEvent在作者取消订阅之后运行,但他们只是取消订阅以避免发生这种情况.当然,真正需要的是一个自定义事件实现,并在add和remove访问器中进行适当的同步.此外,如果在触发事件时保持锁定,则存在可能发生死锁的问题.
那货物崇拜编程也是如此?这似乎是这样 - 许多人必须采取这一步骤来保护他们的代码免受多线程的攻击,而实际上在我看来事件需要更多的关注才能被用作多线程设计的一部分. .因此,没有采取额外关注的人可能会忽略这一建议 - 这对于单线程程序来说根本不是问题,事实上,鉴于volatile大多数在线示例代码缺少,建议可能没有效果.
(并且delegate { }在成员声明中分配空,以便您从不需要首先检查,这不是更简单null吗?)
更新:如果不清楚,我确实掌握了建议的意图 - 在所有情况下避免空引用异常.我的观点是,这个特定的空引用异常只有在另一个线程从事件中退出时才会发生,这样做的唯一原因是确保不会通过该事件接收到进一步的调用,这显然不是通过这种技术实现的. .你会隐瞒竞争条件 - 揭示它会更好!该null异常有助于检测组件的滥用情况.如果您希望保护组件免受滥用,可以按照WPF的示例 - 将线程ID存储在构造函数中,然后在另一个线程尝试直接与组件交互时抛出异常.或者实现真正的线程安全组件(不是一件容易的事).
所以我认为仅仅做这个复制/检查成语是货物崇拜编程,为你的代码添加混乱和噪音.要实际防范其他线程需要更多的工作.
更新以回应Eric Lippert的博文:
因此,对于事件处理程序我有一个重要的事情:"即使在事件被取消订阅后,事件处理程序也必须是强大的,"因此显然我们只需关心事件的可能性代表是null.对事件处理程序的要求是否记录在何处?
所以:"还有其他方法可以解决这个问题;例如,初始化处理程序以使一个永不删除的空操作.但是进行空检查是标准模式."
所以我的问题的另一个片段是,为什么显式 - 空 - 检查"标准模式"?另外,分配空委托只= delegate {}需要添加到事件声明中,这样就可以从事件发生的每个地方消除那些一堆臭味的仪式.很容易确保空委托实例化很便宜.还是我还缺少什么?
肯定一定是(正如Jon Skeet建议的那样)这只是.NET 1.x的建议,并没有像2005年应该做的那样消失吗?
Jon*_*eet 98
由于条件的原因,JIT不允许在第一部分中执行您正在讨论的优化.我知道这是作为一个幽灵提出的,但它无效.(我刚才和Joe Duffy或Vance Morrison一起检查过;我不记得是哪一个.)
如果没有volatile修饰符,那么本地副本可能会过时,但就是这样.它不会导致NullReferenceException.
是的,肯定存在竞争条件 - 但总会如此.假设我们只是将代码更改为:
TheEvent(this, EventArgs.Empty);
Run Code Online (Sandbox Code Playgroud)
现在假设该委托的调用列表有1000个条目.在另一个线程取消订阅列表末尾附近的处理程序之前,列表开头的操作完全可能已执行.但是,该处理程序仍将执行,因为它将是一个新列表.(代表是不可改变的.)据我所知,这是不可避免的.
使用空委托当然可以避免无效检查,但不能修复竞争条件.它也不能保证您始终"看到"变量的最新值.
JP *_*oto 50
我看到很多人都在采用这种扩展方法......
public static class Extensions
{
public static void Raise<T>(this EventHandler<T> handler,
object sender, T args) where T : EventArgs
{
if (handler != null) handler(sender, args);
}
}
Run Code Online (Sandbox Code Playgroud)
这为您提供了更好的语法来提升事件......
MyEvent.Raise( this, new MyEventArgs() );
Run Code Online (Sandbox Code Playgroud)
并且还取消了本地副本,因为它是在方法调用时捕获的.
Dan*_*nov 33
我怀疑这可能是因为空检查更具性能.
如果您在创建事件时始终为事件订阅空委托,则会产生一些开销:
(请注意,UI控件通常具有大量事件,其中大多数事件从未订阅过.必须为每个事件创建一个虚拟订阅者然后调用它可能会对性能造成重大影响.)
我做了一些粗略的性能测试,看看subscribe-empty-delegate方法的影响,这是我的结果:
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took: 432ms
OnClassicNullCheckedEvent took: 490ms
OnPreInitializedEvent took: 614ms <--
Subscribing an empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took: 674ms
OnClassicNullCheckedEvent took: 674ms
OnPreInitializedEvent took: 2041ms <--
Subscribing another empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took: 2011ms
OnClassicNullCheckedEvent took: 2061ms
OnPreInitializedEvent took: 2246ms <--
Done
Run Code Online (Sandbox Code Playgroud)
请注意,对于零个或一个订阅者的情况(UI控件常见,事件很多),使用空委托预先初始化的事件明显较慢(超过5000万次迭代......)
有关更多信息和源代码,请访问我在此问题被提出前一天发布的.NET事件调用线程安全的博客文章(!)
(我的测试设置可能存在缺陷,因此请随意下载源代码并自行检查.非常感谢任何反馈.)
Chu*_*age 11
我真的很喜欢这个读 - 不!即使我需要它来使用称为事件的C#功能!
为什么不在编译器中修复它?我知道有MS人阅读这些帖子,所以请不要点燃这个!
1 - Null问题)为什么不首先使事件成为.Empty而不是null?将多少行代码保存用于空检查或必须粘贴= delegate {}到声明上?让编译器处理Empty case,IE什么都不做!如果这对事件的创建者都很重要,他们可以检查.Empty并做任何他们关心的事情!否则所有null检查/委托添加都是围绕问题的黑客!
老实说,我已经厌倦了每次活动都必须这样做 - 也就是样板代码!
public event Action<thisClass, string> Some;
protected virtual void DoSomeEvent(string someValue)
{
var e = Some; // avoid race condition here!
if(null != e) // avoid null condition here!
e(this, someValue);
}
Run Code Online (Sandbox Code Playgroud)
2 - 竞争条件问题)我读过Eric的博客文章,我同意H(处理程序)应该在它自我解除引用时处理,但是不能使事件变为不可变/线程安全吗?IE,在其创建时设置一个锁定标志,这样无论何时调用它,它都会在执行时锁定所有订阅和取消订阅?
结论,
是不是现代语言应该为我们解决这些问题?
根据Jeffrey Richter在书中通过C#的书,正确的方法是:
// Copy a reference to the delegate field now into a temporary field for thread safety
EventHandler<EventArgs> temp =
Interlocked.CompareExchange(ref NewMail, null, null);
// If any methods registered interest with our event, notify them
if (temp != null) temp(this, e);
Run Code Online (Sandbox Code Playgroud)
因为它强制引用副本.有关更多信息,请参阅本书中的"事件"部分.