微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

这是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参数相同的锁定对象;如果传递任何其他对象,那么整个讨论是无效的.)

原文地址:https://www.jb51.cc/csharp/94839.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐