2

我正在寻找可能对此了解更多的人,我的直觉告诉我答案是“不,它不是线程安全的”,但我想确定。

为了说明我的问题,我提供了这个类的一些背景信息

public class MyContext
{
    private readonly object _lock = new object();
    public delegate bool MyDelegate(MyContext context);
    private MyDelegate _multicastDelegate;

    public MyContext()
    {
        _multicastDelegate = null;
    }

    public void AddDelegate(MyDelegate del)
    {
        lock(_lock)
        {
            _multicastDelegate += del;
        }
    }

    public void RemoveDelegate(MyDelegate del)
    {
        lock(_lock)
        {
            _multicastDelegate += del;
        }
    }

    public void Go()
    {
        _multicastDelegate.Invoke(this);
    }
}

编辑:我在上面的示例中添加了锁,但这真的不是我问题的重点。


我试图更好地理解保存调用列表的数组是否是线程安全的。坦率地说,我不清楚这一切是如何组合在一起的,我们将不胜感激。

根据我发现的文档,唯一没有提供真正洞察力的报价如下:

MulticastDelegate 有一个委托的链接列表,称为调用列表,由一个或多个元素组成。调用多播委托时,调用列表中的委托按照它们出现的顺序被同步调用。如果在执行列表期间发生错误,则会引发异常。

https://msdn.microsoft.com/en-us/library/system.multicastdelegate.aspx

提前致谢。

4

2 回答 2

6

代表不可变的。您永远不会更改代表。任何看似改变委托的方法实际上都是在创建一个新实例。

代表是不可变的;一旦创建,委托的调用列表就不会改变。

因此,无需担心调用列表可能会在调用委托时更新。

但是,您必须防止并且在您的方法中未能做到的是委托实际上可能是null.

(new MyContext()).Go();

会导致异常。您过去必须通过将值读取到局部变量中来防止这种情况发生,测试它是否为空,然后使用它调用。它现在可以更容易地解决为:

public void Go()
{
    _multicastDelegate?.Invoke(this);
}
于 2018-02-08T10:49:28.683 回答
0

MSDN 文档中使用的线程安全的定义意味着正确同步的代码。它通常不会说明它同步的内容,但它可以是静态成员的类对象、实例成员的实例对象,或者它可以是一些内部对象,例如SyncRoot在许多集合类型中。

尽管委托是不可变的,但您仍然必须正确同步。.NET 和 C# 与 Java 不同,不保证安全发布,因此如果不保证同步,您可以在其他线程中观察部分初始化的对象1

要使您的代码线程安全,您只需要_lock在从委托字段中读取时使用,但您可以Invoke在锁外调用,让委托负责保持其自身的线程安全。

public class MyContext
{
    private readonly object _lock = new object();
    public delegate bool MyDelegate(MyContext context);
    private MyDelegate _delegate;

    public MyContext()
    {
    }

    public void AddDelegate(MyDelegate del)
    {
        lock (_lock)
        {
            _delegate += del;
        }
    }

    public void RemoveDelegate(MyDelegate del)
    {
        lock (_lock)
        {
            // You had a bug here, +=
            _delegate -= del;
        }
    }

    public void Go()
    {
        MyDelegate currentDelegate;
        lock (_lock)
        {
            currentDelegate = _delegate;
        }
        currentDelegate?.Invoke(this);
    }
}

  1. Microsoft 的 .NET Framework 实现总是进行 volatile 写入(或者他们这么说),这隐含地为您提供了安全的发布,但我个人并不依赖于此。
于 2018-02-08T12:02:00.897 回答