Wil*_*den 10 c# events multithreading locking thread-safety
更新:为了所有阅读本文的人的利益,自.NET 4起,由于自动生成事件同步的变化,锁定是不必要的,所以我现在就使用它:
public static void Raise<T>(this EventHandler<T> handler, object sender, T e) where T : EventArgs
{
if (handler != null)
{
handler(sender, e);
}
}
Run Code Online (Sandbox Code Playgroud)
并提出它:
SomeEvent.Raise(this, new FooEventArgs());
Run Code Online (Sandbox Code Playgroud)
在阅读过Jon Skeet 关于多线程的文章之后,我试图将他提倡的方法封装在像这样的扩展方法中引发事件(使用类似的通用版本):
public static void Raise(this EventHandler handler, object @lock, object sender, EventArgs e)
{
EventHandler handlerCopy;
lock (@lock)
{
handlerCopy = handler;
}
if (handlerCopy != null)
{
handlerCopy(sender, e);
}
}
Run Code Online (Sandbox Code Playgroud)
然后可以这样调用:
protected virtual void OnSomeEvent(EventArgs e)
{
this.someEvent.Raise(this.eventLock, this, e);
}
Run Code Online (Sandbox Code Playgroud)
这样做有什么问题吗?
另外,我首先对锁的必要性感到有些困惑.据我所知,委托被复制到文章的示例中,以避免在null检查和委托调用之间更改(并变为null)的可能性.但是,我认为这种访问/分配是原子的,为什么锁是必要的呢?
更新:关于Mark Simpson在下面的评论,我总结了一个测试:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
这输出:
Foo
Bar
Foo
Bar
Run Code Online (Sandbox Code Playgroud)
这说明method(action)的delegate参数不会镜像传递给它的参数(test),我想这是预期的.我的问题是,这会影响我的Raise扩展方法上下文中锁的有效性吗?
更新:这是我现在使用的代码.它并不像我喜欢的那么优雅,但似乎有效:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
锁定的目的是在覆盖默认事件连线时保持线程安全.如果其中一些内容解释了你已经能够从Jon的文章中推断出的东西,那就道歉了; 我只是想确保我对一切都完全清楚.
如果你宣布你的事件是这样的:
public event EventHandler Click;
Run Code Online (Sandbox Code Playgroud)
然后订阅该事件将自动与a同步lock(this).你不是不需要写任何特殊的锁定代码来调用事件处理程序.写完是完全可以接受的:
var clickHandler = Click;
if (clickHandler != null)
{
clickHandler(this, e);
}
Run Code Online (Sandbox Code Playgroud)
但是,如果您决定覆盖默认事件,即:
public event EventHandler Click
{
add { click += value; }
remove { click -= value; }
}
Run Code Online (Sandbox Code Playgroud)
现在你遇到了问题,因为不再有隐式锁定了.您的事件处理程序刚刚失去了线程安全性.这就是你需要使用锁的原因:
public event EventHandler Click
{
add
{
lock (someLock) // Normally generated as lock (this)
{
_click += value;
}
}
remove
{
lock (someLock)
{
_click -= value;
}
}
}
Run Code Online (Sandbox Code Playgroud)
就我个人而言,我并不担心这一点,但乔恩的理由是合理的.但是,我们确实有一个小问题.如果您使用私有EventHandler字段来存储您的事件,那么您可能拥有执行此操作的类的内部代码:
protected virtual void OnClick(EventArgs e)
{
EventHandler handler = _click;
if (handler != null)
{
handler(this, e);
}
}
Run Code Online (Sandbox Code Playgroud)
这很糟糕,因为我们访问相同的私有存储字段而不使用属性使用的相同锁.
如果该类外部的一些代码:
MyControl.Click += MyClickHandler;
Run Code Online (Sandbox Code Playgroud)
通过公共财产的外部代码正在兑现锁定.但你不是,因为你正在触摸私人领域.
该变量赋值的一部分clickHandler = _click是原子的,是的,但分配时,该_click字段可能处于过渡状态,一个一个已经由外部类半写的.当您同步对字段的访问时,仅仅同步写访问权限是不够的,您还必须同步读取访问权限:
protected virtual void OnClick(EventArgs e)
{
EventHandler handler;
lock (someLock)
{
handler = _click;
}
if (handler != null)
{
handler(this, e);
}
}
Run Code Online (Sandbox Code Playgroud)
UPDATE
事实证明,围绕评论的一些对话实际上是正确的,正如OP的更新所证明的那样.这不是扩展方法本身的问题,它是委托具有值类型语义并在赋值时被复制的事实.即使您this从扩展方法中取出并仅将其作为静态方法调用,您也会得到相同的行为.
您可以使用静态实用程序方法绕过此限制(或功能,具体取决于您的观点),但我很确定您无法使用扩展方法.这是一个可行的静态方法:
public static void RaiseEvent(ref EventHandler handler, object sync,
object sender, EventArgs e)
{
EventHandler handlerCopy;
lock (sync)
{
handlerCopy = handler;
}
if (handlerCopy != null)
{
handlerCopy(sender, e);
}
}
Run Code Online (Sandbox Code Playgroud)
这个版本有效,因为我们实际上并没有传递它EventHandler,只是对它的引用(注意ref方法签名中).遗憾的是,您不能在扩展方法中使用ref它this,因此它必须保持纯静态方法.
(如前所述,您必须确保传递的锁对象与sync您在公共事件中使用的参数相同;如果您传递任何其他对象,则整个讨论都没有实际意义.)
| 归档时间: |
|
| 查看次数: |
2332 次 |
| 最近记录: |