这是C#中提升事件的有效模式吗?
更新:为了让读者看到这一点的利益,由于.NET 4,由于自动生成事件的同步发生变化,所以锁是不必要的,所以我现在就用这个??:
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参数相同的锁定对象;如果传递任何其他对象,那么整个讨论是无效的.) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |