C#:线程安全事件

Svi*_*ish 5 c# events multithreading event-handling thread-safety

下面的实现是否是线程安全的?如果不是我错过了什么?我应该在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*eet 5

您也需要在获取处理程序时锁定,否则您可能没有最新值:

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

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

除了让处理程序本身意识到它不应再被调用之外,你可以做很多事情.

最好不要尝试使事件成为线程安全的 - 指定订阅应在引发事件的线程中更改.

  • 即使有锁,也无法保证你能看到最新的价值.那是因为它取决于谁是第一个:转换器或调用者.所以你只需要锁定添加/删除因为两者都是内存中的两个步骤:读写在读取和写入之间,另一个转换器可以读取和写入,因此转换器1会覆盖转换器2的更改.但这不会打扰调用者.它可以在转换器的读写之间读取,但我没有看到任何问题. (如果两者同时或多或少地运行,则无法确定调用者获得的值;有或没有锁定) (5认同)

Mar*_*ell 4

私有成员不需要ProcessCompletedevent- 它可能只是一个字段:private EventHandler<ProcessCompletedEventArgs> ProcessCompleted;- 在类内部它总是直接进入该字段,所以event无论如何东西都会丢失。

您使用显式锁定对象展示的方法并不比仅具有类似字段的事件更线程安全(即-public event EventHandler<ProcessCompletedEventArgs> ProcessCompleted;唯一的区别是您没有锁定“this”(这是一件好事 -理想情况下,您应该避免锁定this)。“处理程序变量”方法是正确的方法,但您仍然应该注意一些副作用

  • 是的; 类似字段的事件(即没有显式添加/删除的事件)具有内置锁(this);参见语言规范(MS 版本)中的 10.8.1;但是,类内部的代码绕过了这一点 - 请参阅http://marcgravell.blogspot.com/2009/02/fun-with-field-like-events.html;因此,作为*私有*事件,添加/删除(以及锁定)永远不会被使用。对于显式接口实现,代码很好,您需要自己添加锁,您已经这样做了 - 并且可以说比“锁(this)”*更好*。坚持下去;-p (2认同)