【问题标题】:How make custom Thread Safe Generic List return the whole list in C#?如何使自定义线程安全通用列表返回 C# 中的整个列表?
【发布时间】:2012-08-31 01:29:05
【问题描述】:

我是一个线程新手,我正在尝试用 C# (.NET 3.5 SP1) 编写一个自定义线程安全的通用列表类。我读过Why are thread safe collections so hard?。在查看了课程的要求后,我认为我只需要安全地添加到列表中并返回列表.该示例几乎显示了我想要的所有内容,只是它缺少 return list 方法,因此我编写了自己的公共方法,如下所示:

更新:根据给出的建议,我已经审查了我的要求,因此将课程简化为如下:

public sealed class ThreadSafeList<T>
{
    private readonly IList<T> list = new List<T>();
    private readonly object lockable = new object();

    public void Add(T t)
    {
        lock (lockable)
        {
            list.Add(t);
        }
    }

    public IList<T> GetSnapshot()
    {
        IList<T> result;
        lock (lockable)
        {
            result = new List<T>(list);
        }
        return result;
    }
}

【问题讨论】:

  • 看起来更好。我要做的最后一件事是将 Translate() 返回的列表设为只读。读取列表中的内容应该是一个固有的只读操作...通过允许从 Translate() 返回的列表被添加到,您为添加到它的人打开了一扇门,这可能会造成一些丑陋的不一致。我会改变'返回结果;' '返回结果.AsReadOnly();'
  • 好吧,我仍然对您的特定实现有疑问。 ReadOnlyCollection 只是一个包装器......它不会复制您传递给其构造函数的列表,它只是提供对它的只读访问。因此,如果您尝试枚举 Translate() 返回的列表,如果在枚举发生时有人调用 ThreadSafeList.Add(),您可能会遇到异常。我仍然建议显式复制列表并执行 return result.AsReadOnly() 以防止这种情况发生。它使用更多内存,但不会遇到任何线程问题。
  • 线程很难,感谢所有贡献的人!

标签: c# list thread-safety generic-list


【解决方案1】:

同意@jrista。有一个语义问题需要解决,为什么叫Translate()?目的是什么?

A - 当前代码 - 返回内部列表的只读包装

return new ReadOnlyCollection<T>(list);

如果另一个线程正在迭代列表,则如果更改了原始列表,您仍然会遇到线程问题。只要你意识到这一点,这不是一个大问题。

B - 只读副本。

return new List<T>(list).AsReadOnly();

此列表没有线程问题,因为没有任何内容会修改新列表。唯一持有的引用是 ReadOnlyCollection&lt;T&gt; 包装器。

C - 普通(可写)副本

return new List<T>(list);

返回一个新列表,调用者可以在不影响原始列表的情况下对列表进行任何操作,对原始列表的更改不会影响此列表。


如果另一个消费者获取列表的副本然后修改他们的副本,这有关系吗?消费者是否需要查看列表的更改?您只需要一个线程安全的枚举器吗?

public IEnumerator<T> ThreadSafeEnumerator()
{
    List<T> copy;
    lock(lockable)
        copy = new List<T>(list);

    foreach (var value in copy)
        yield return value;
}

【讨论】:

  • @Robert,您最后几个问题的答案是否定的。感谢您的洞察力解释。我已将方法的名称更新为 GetSnapshot(),我相信现在它可以更好地解释意图。
  • @Jeffrey 很酷。 GetSnapshot() 是一个更好的名字。干得好。
【解决方案2】:

根据我的经验,在线程安全方面,您必须动脑筋,而不是依赖诸如此类的解决方案。 简而言之,这取决于列表的接收者将如何处理它。

【讨论】:

  • 请详细说明“不要依赖诸如此类的解决方案”。谢谢。
  • 我相信他的意思是在内部处理线程同步的解决方案。您的 Add 方法返回 INTERNAL 列表,该列表未同步,允许该列表的接收者随时随地做他们想做的任何事情,无论是否锁定。即使他们锁定......这将是他们锁定的不同对象,并且在那时同步是不可能的。您应该将线程同步留给消费者,而不是尝试在内部处理它。编写一个连贯的线程保存并发集合并非易事。
  • @jrista,实际上这个问题是我之前的问题stackoverflow.com/questions/2322557 的后续问题,我相信这个 ThreadSafeList 类实际上是在消费者中使用的??? (调用方法一和方法二的方法)。
  • 在哪里使用 ThreadSafeList 并不重要。简单的事实是,从线程的角度来看,您的 Add 方法是完全有缺陷的。它将不同步的内部状态返回给调用者。如果您希望您的 ThreadSafeList 实际上是安全的,您需要更改返回的内容。要么不返回任何内容,要么返回所添加条目的索引。但不要返回任何内部状态......否则你的锁定是无用的。
  • @jrista,我认为您所描述的内容已修复。
【解决方案3】:

Translate() 方法看起来是正确的。当您在 Translate/AddRange 中时,使用锁可以防止其他人添加或以其他方式修改您的列表。

我认为您的 IsReadyOnly 属性可能存在问题。在内部读取/写入属性时使用锁。但也有一个未锁定的公共吸气剂。线程 1 可能会调用 MarkAsReadOnly,而第二个线程在查看 IsReadOnly 时可能仍会出错。我会改用普通属性并锁定 getter 或使用 volatile bool 字段。

【讨论】:

    【解决方案4】:

    您可以使用 SynchronizedCollection。

    http://msdn.microsoft.com/en-us/library/ms668265.aspx

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-25
      • 1970-01-01
      相关资源
      最近更新 更多