【问题标题】:Is ReadOnlyCollection threadsafe if the underlying collection is not touched?如果未触及底层集合,ReadOnlyCollection 是否是线程安全的?
【发布时间】:2010-12-18 00:38:37
【问题描述】:

MSDN 隐约提到:

一个 ReadOnlyCollection)>) 可以同时支持多个读取器,只要集合没有被修改。 即便如此,通过集合进行枚举本质上不是线程安全的过程。为了保证枚举过程中的线程安全,可以在整个枚举过程中锁定集合。要允许集合被多个线程访问以进行读写,您必须实现自己的同步。

以下公共静态集合对于多个线程迭代是否安全?如果没有,.NET 中是否有一些安全的东西?我是否应该删除 ReadOnlyCollection 并为 SomeStrings 属性获取器的每次访问创建一个私有集合的新副本?我知道如果多个线程试图锁定公共集合可能会出现死锁问题,但这是一个内部库,我不明白我们为什么要这样做。

public static class WellKnownStrings {

    public static readonly ICollection<string> SomeStrings;

    static WellKnownStrings()
    {
        Collection<string> someStrings = new Collection<string>();
        someStrings.Add("string1");
        someStrings.Add("string2");
        SomeStrings = new ReadOnlyCollection<string>(someStrings);
    }
}

【问题讨论】:

  • +1 - 很好的问题。我认为文档中使用的语言也令人困惑;它从来没有明确说明多个线程是否可以安全地枚举一个集合,只要该集合在此期间没有被修改。 (我相信答案是“它是安全的”,但希望看到明确的回应。)

标签: .net collections


【解决方案1】:

通常,永远不会改变其内部状态(一旦发布给外部调用者)的不可变对象可以被视为线程安全的。

然而,ReadOnlyCollection&lt;T&gt; 本身并不是线程安全的,因为它只是一个现有集合的包装器,它的所有者可以随时修改它。

不过,OP 中的示例是线程安全的,因为无法修改底层集合(至少在没有黑客攻击的情况下无法修改)。

【讨论】:

  • 谢谢,我就是这么想的,所以当他们说这可能不是线程安全的时候,他们说的取决于使用情况。如果使用没问题,那么它将是线程安全的。
  • 投反对票的人是否愿意详细说明他们投反对票的原因?
  • 这个答案对我来说似乎是正确的,但我认为将其标记为已接受会有点确认偏差,没有来自语言规范的引用,或者可能是通过反射器对代码进行静态分析等.
【解决方案2】:

你想要一些强类型吗?

虽然您的解决方案很聪明,但我认为这可能更适合您的需求,尤其是在代码重用方面。

众所周知的字符串

public class WellKnownStrings : StringEnumeration
{

    private WellKnownStrings(string specialString) :base(specialString)
    {

    }
    public static IEnumerable<String> SpecialStrings
    {
        get
        {
            return GetAllStrings<WellKnownStrings>();
        }
    }

    public static readonly WellKnownStrings String1 = new WellKnownStrings("SOME_STRING_1");
    public static readonly WellKnownStrings String2 = new WellKnownStrings("SOME_STRING_2_SPECIAL");
    public static readonly WellKnownStrings String3 = new WellKnownStrings("SOME_STRING_3_SPECIAL"); 
}

字符串枚举

这是一个基类,我已经根据您的描述进行了调整。

public abstract class StringEnumeration : Enumeration
{

    private static int _nextItemValue;
    private static readonly object _initializeLock = new object();


    protected StringEnumeration(string stringValue)
        :base(0, stringValue)
    {
        if(stringValue == null)
        {
            throw new ArgumentNullException("stringValue");
        }
        lock(_initializeLock)
        {
            _nextItemValue++;
            _value = _nextItemValue;
        }
    }

    public static IEnumerable<string> GetAllStrings<T>()
        where T: StringEnumeration
    {
        return GetAll<T>().Select(x => x.DisplayName);
    }

    private readonly int _value;
    public override int  Value
    {
        get 
        {
            return _value;
        }
    }


    public static explicit operator string(WellKnownStrings specialStrings)
    {
        return specialStrings.ToString();
    }


}

枚举基类

Code originally stolen and adapted from Jimmy Bogard's blog 我所做的唯一更改是使派生类中的Value 属性为虚拟,并使GetAll() 不依赖于new T() 泛型参数,因为静态成员字段不需要实例来反射地获取值。

public abstract class Enumeration : IComparable
{
    private readonly int _value;
    private readonly string _displayName;


    protected Enumeration(int value, string displayName)
    {
        _value = value;
        _displayName = displayName;
    }

    public virtual int Value
    {
        get { return _value; }
    }

    public string DisplayName
    {
        get { return _displayName; }
    }

    public override string ToString()
    {
        return DisplayName;
    }

    public static IEnumerable<T> GetAll<T>() where T : Enumeration 
    {
        return typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
            .Where(field => field.FieldType == typeof (T))
            .Select(field => field.GetValue(null))
            .Where(value =>value != null)
            .Cast<T>();
    }

    public override bool Equals(object obj)
    {
        var otherValue = obj as Enumeration;

        if (otherValue == null)
        {
            return false;
        }

        var typeMatches = GetType().Equals(obj.GetType());
        var valueMatches = _value.Equals(otherValue.Value);

        return typeMatches && valueMatches;
    }

    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }

    public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
    {
        var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value);
        return absoluteDifference;
    }

    public static T FromValue<T>(int value) where T : Enumeration, new()
    {
        var matchingItem = parse<T, int>(value, "value", item => item.Value == value);
        return matchingItem;
    }

    public static T FromDisplayName<T>(string displayName) where T : Enumeration, new()
    {
        var matchingItem = parse<T, string>(displayName, "display name", item => item.DisplayName == displayName);
        return matchingItem;
    }

    private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new()
    {
        var matchingItem = GetAll<T>().FirstOrDefault(predicate);

        if (matchingItem == null)
        {
            var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(T));
            throw new Exception(message);
        }

        return matchingItem;
    }

    public int CompareTo(object other)
    {
        return Value.CompareTo(((Enumeration)other).Value);
    }
}

    public static IEnumerable<T> GetAll<T>() where T : Enumeration, new()
    {
        var type = typeof(T);
        var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(field=>);

        foreach (var info in fields)
        {
            var instance = new T();
            var locatedValue = info.GetValue(instance) as T;

            if (locatedValue != null)
            {
                yield return locatedValue;
            }
        }
    }

    public override bool Equals(object obj)
    {
        var otherValue = obj as Enumeration;

        if (otherValue == null)
        {
            return false;
        }

        var typeMatches = GetType().Equals(obj.GetType());
        var valueMatches = _value.Equals(otherValue.Value);

        return typeMatches && valueMatches;
    }

    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }

    public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
    {
        var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value);
        return absoluteDifference;
    }

    public static T FromValue<T>(int value) where T : Enumeration, new()
    {
        var matchingItem = parse<T, int>(value, "value", item => item.Value == value);
        return matchingItem;
    }

    public static T FromDisplayName<T>(string displayName) where T : Enumeration, new()
    {
        var matchingItem = parse<T, string>(displayName, "display name", item => item.DisplayName == displayName);
        return matchingItem;
    }

    private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new()
    {
        var matchingItem = GetAll<T>().FirstOrDefault(predicate);

        if (matchingItem == null)
        {
            var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(T));
            throw new Exception(message);
        }

        return matchingItem;
    }

    public int CompareTo(object other)
    {
        return Value.CompareTo(((Enumeration)other).Value);
    }
}

另外,关于线程安全问题...

我提供的类是线程安全的、直观的和可重用的。您的ReadOnlyCollection&lt;T&gt; 用例也是线程安全的,但是(正如herzmeister der welten)指出的那样,在许多情况下并非如此。它实际上也没有公开可写的 ICollection 成员,因为对这些成员的任何调用都会引发异常。

【讨论】:

  • 虽然这个答案可能会为 OP 的特定用途提供解决方案,但我奖励一般问题的答案,我觉得 herzmeister der welten 答案更有效地解决了这个问题。不过感谢您的参与,希望这将有助于 OP 或其他出现的人。
  • 应该是“私有只读对象_initializeLock = new object();”是静态的?在构造函数中锁定成员变量没有任何作用......老实说,我看不到您的解决方案的好处。两者都返回一个只读的 Enumerable 字符串,未来的维护者不需要 15 分钟就能理解。
  • @Chris,是的 - _initializeLock 应该是静态的 - 我更改了代码以反映这一点。这种方法的优点是为枚举提供了强类型。因此,例如,假设您想要一个方法采用特殊字符串之一的参数。您可以将参数类型设为 WellKnownStrings 而不是字符串。这可以适应许多不同的用途 - 我在帖子中包含了一篇文章,解释了一些好处
【解决方案3】:

如果有人有兴趣知道我在这里做了什么,在看到 Jon Skeet 的 this answer(当然)之后,我选择了这个:

public static class WellKnownStrings
{
    public const string String1= "SOME_STRING_1";

    public const string String2= "SOME_STRING_2_SPECIAL";

    public const string String3= "SOME_STRING_3_SPECIAL";

    public static IEnumerable<string> SpecialStrings
    {
        get
        {
            yield return String2;
            yield return String3;
        }
    }
}

它不会为调用者提供 ICollection&lt;T&gt; 的其余功能,但在我的情况下不需要。

【讨论】:

  • 解决这种特殊情况的巧妙方法。
【解决方案4】:

我会说来自并行扩展的ConcurrentCollection&lt;T&gt; 可以解决问题,对吗?您总是可以说没有人可以将任何项目添加到集合(公开集合)并且您已设置。 卢克

【讨论】:

    猜你喜欢
    • 2012-01-26
    • 1970-01-01
    • 2013-02-15
    • 1970-01-01
    • 1970-01-01
    • 2012-08-21
    • 2021-12-19
    • 1970-01-01
    相关资源
    最近更新 更多