【问题标题】:C#: Thread-safe eventsC#:线程安全事件
【发布时间】:2010-11-05 11:40:44
【问题描述】:

下面的实现是线程安全的吗?如果不是,我错过了什么?我应该在某处使用volatile 关键字吗?还是OnProcessingCompleted 方法中某处的锁?如果有,在哪里?

public abstract class ProcessBase : IProcess
{
    private readonly object completedEventLock = new object();

    private event EventHandler<ProcessCompletedEventArgs> ProcessCompleted;

    event EventHandler<ProcessCompletedEventArgs> IProcess.ProcessCompleted
    {
        add
        {
            lock (completedEventLock)
                ProcessCompleted += value;
        }
        remove
        {
            lock (completedEventLock)
                ProcessCompleted -= value;
        }
    }

    protected void OnProcessingCompleted(ProcessCompletedEventArgs e)
    {
        EventHandler<ProcessCompletedEventArgs> handler = ProcessCompleted;
        if (handler != null)
            handler(this, e);
    }
}

注意:我之所以有私有事件和显式接口的东西,是因为它是一个抽象基类。从它继承的类不应该直接对该事件做任何事情。添加了类包装器,使其更清晰 =)

【问题讨论】:

    标签: c# multithreading events event-handling thread-safety


    【解决方案1】:

    你在获取handler的时候也需要加锁,否则你可能没有最新的值:

    protected void OnProcessingCompleted(ProcessCompletedEventArgs e)
    {
        EventHandler<ProcessCompletedEventArgs> handler;
        lock (completedEventLock) 
        {
            handler = ProcessCompleted;
        }
        if (handler != null)
            handler(this, e);
    }
    

    请注意,这不会阻止我们决定执行一组处理程序并且然后取消订阅一个处理程序的竞争条件。它仍会被调用,因为我们已将包含它的多播委托提取到 handler 变量中。

    除了让处理程序本身意识到不应再调用它之外,您无能为力。

    最好不要尝试使事件线程安全 - 指定订阅应该在将引发事件的线程中更改。 p>

    【讨论】:

    • 你确定,锁是必要的吗?代表是不可变的,而分配是原子操作,所以我认为不需要锁。
    • 在您的帖子中查看我的 cmets。您绝对需要锁定以使其成为线程安全的。
    • 是的,“添加”和“删除”中的锁是必要的。但是在 "OnProcessingCompleted" 中使用 "lock" 有什么好处呢?
    • 没有锁,就没有内存屏障,所以不能保证你会看到最新的值。见pobox.com/~skeet/csharp/threads/volatility.shtml
    • 即使有锁也不能保证你看到最新的值。那是因为它取决于谁先来:更改者或调用者。所以你只需要添加/删除的锁,因为两者都是内存中的两个步骤:读取和写入。因此在读取和写入之间,另一个转换器可以读取和写入,因此转换器 1 覆盖转换器 2 的更改。但这不会打扰调用者。它可能在转换器的读取和写入之间读取,但我不'看不出有什么问题。(如果两者或多或少同时运行,您无法判断调用者获得的值;有或没有锁定)
    【解决方案2】:

    私有ProcessCompleted 成员不需要是event - 它可以只是一个字段:private EventHandler&lt;ProcessCompletedEventArgs&gt; ProcessCompleted; - 在类内部它总是直接进入字段,所以event 的东西是还是输了。

    您使用显式锁定对象显示的方法更多比仅具有类似字段的事件(即public event EventHandler&lt;ProcessCompletedEventArgs&gt; ProcessCompleted; - 唯一的区别是您是'不锁定“this”(这是一件好事——你应该理想地避免锁定this)..“处理程序变量”方法是正确的,但仍有side-effects you should be aware of .

    【讨论】:

    • 在我的问题中添加了为什么我使用私有事件处理程序和显式事件内容。还是不需要吗?您所说的这种差异是什么意思?公共事件 EventHandler SomeEvent 会自动锁定它吗?
    • 是的;类字段事件(即没有显式添加/删除的事件)具有内置锁(this);参见语言规范(MS 版)中的 10.8.1;然而,这被类内的代码绕过了——见marcgravell.blogspot.com/2009/02/…;因此,作为 private 事件,永远不会使用添加/删除(因此锁定)。对于显式接口实现,代码很好,您需要自己添加锁,您已经完成了 - 并且可以说比“lock(this)”更好。坚持下去;-p
    猜你喜欢
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多