public static void Raise<T>(this EventHandler<T> handler,object sender,T e) where T : EventArgs { if (handler != null) { handler(sender,e); } }
并提高它:
SomeEvent.Raise(this,new FooEventArgs());
一直在阅读Jon Skeet的articles on multithreading之一,我试图封装他主张通过扩展方法提出一个事件的方法(具有类似的通用版本):
public static void Raise(this EventHandler handler,object @lock,EventArgs e) { EventHandler handlerCopy; lock (@lock) { handlerCopy = handler; } if (handlerCopy != null) { handlerCopy(sender,e); } }
这样可以这样调用:
protected virtual void OnSomeEvent(EventArgs e) { this.someEvent.Raise(this.eventLock,this,e); }
这样做有什么问题吗?
此外,我有点混淆了锁的必要性在第一位.据了解,代理在文中的示例中被复制,以避免在null检查和委托调用之间改变(并变为null)的可能性.但是,我的印象是这种访问/转让是原子的,所以为什么锁是必需的?
更新:关于马克辛普森在下面的评论,我投了一个考验:
static class Program { private static Action foo; private static Action bar; private static Action test; static void Main(string[] args) { foo = () => Console.WriteLine("Foo"); bar = () => Console.WriteLine("Bar"); test += foo; test += bar; test.Test(); Console.ReadKey(true); } public static void Test(this Action action) { action(); test -= foo; Console.WriteLine(); action(); } }
输出:
Foo Bar Foo Bar
这表明方法(action)的delegate参数不会镜像传入它的参数(test),这是我预料的.我的问题是这会影响我在升级扩展方法的上下文中的锁的有效性吗?
更新:这是我正在使用的代码.它不像我想要的那样优雅,但似乎工作:
public static void Raise<T>(this object sender,ref EventHandler<T> handler,object eventLock,T e) where T : EventArgs { EventHandler<T> copy; lock (eventLock) { copy = handler; } if (copy != null) { copy(sender,e); } }
解决方法
如果你这样声明你的活动:
public event EventHandler Click;
然后订阅该事件将自动与一个锁同步(这).您不需要编写任何特殊的锁定代码来调用事件处理程序.完全可以写:
var clickHandler = Click; if (clickHandler != null) { clickHandler(this,e); }
但是,如果您决定覆盖默认事件,即:
public event EventHandler Click { add { click += value; } remove { click -= value; } }
现在你有一个问题,因为没有隐式的锁了.你的事件处理程序只是失去了线程安全性.这就是为什么你需要使用锁:
public event EventHandler Click { add { lock (someLock) // Normally generated as lock (this) { _click += value; } } remove { lock (someLock) { _click -= value; } } }
就我个人而言,我不打扰这个,但乔恩的理由是健全的.但是,我们确实有一个小问题.如果您正在使用一个专用的EventHandler字段来存储事件,那么您可能会在类的内部部署代码:
protected virtual void OnClick(EventArgs e) { EventHandler handler = _click; if (handler != null) { handler(this,e); } }
这是坏的,因为我们正在访问相同的私有存储字段,而不使用该属性使用的相同的锁.
如果该类外部的一些代码:
MyControl.Click += MyClickHandler;
通过公共财产的外部代码正在履行锁定.但是你不是,因为你正在触摸私人领域.
clickHandler = _click的变量赋值部分是原子的,是的,但是在该赋值过程中,_click字段可能处于一个暂时状态,一个外部类被半写.当您同步访问某个字段时,只能同步写访问是不够的,您还必须同步读取访问权限:
protected virtual void OnClick(EventArgs e) { EventHandler handler; lock (someLock) { handler = _click; } if (handler != null) { handler(this,e); } }
UPDATE
事实证明,OP的更新证明了围绕评论的一些对话实际上是正确的.这不是扩展方法本身的一个问题,事实上代理具有值类型语义并在赋值时被复制.即使你把这个从扩展方法中拿出来,只是调用它作为一个静态方法,你会得到相同的行为.
尽管我很确定您不能使用扩展方法,但可以使用静态实用程序方法来解决此限制(或特性,具体取决于您的观点).这是一个静态的方法,将工作:
public static void RaiseEvent(ref EventHandler handler,object sync,EventArgs e) { EventHandler handlerCopy; lock (sync) { handlerCopy = handler; } if (handlerCopy != null) { handlerCopy(sender,e); } }
这个版本的工作原理是因为我们实际上并没有传递EventHandler,只是引用它(注意方法签名中的引用).不幸的是,您不能在扩展方法中使用ref,因此它必须保持纯静态方法.
(并且如前所述,您必须确保您传递与您在公共事件中使用的sync参数相同的锁定对象;如果传递任何其他对象,那么整个讨论是无效的.)