锁是否确保列表元素的线程安全?

时间:2016-11-24 00:25:32

标签: c# multithreading list reference locking

据我了解,他们没有。请确认。

假设我有一个包含List< T>的Processor对象。其中T是参考类型。

UI线程实例化Processor对象,并定期调用它以获取List< T>。

处理器对象还启动具有对List< T>的引用的任务。它建成时。

锁用于保留对List< T>的访问权。在getter和任务中确保他们可以独家访问List< T>。

public class Processor
{
  private List<T> _list = new List<T>();
  private Object _listLock = new Object();

  public Processor()
  {
    // populate _list

    Task t = new Task(Process);

    t.Start();
  }

  public List<T> GetList()
  {
    lock(_listLock)
    {
      return _list;
    }
  }

  private void Process()
  {
    while(!doneProcessing)
    {
      lock(_listLock)
      {
        // access and modify _list items
      }

      Thread.Sleep(...);
    }
  }
}

但即使List&lt; T&gt;被锁定在getter中,它返回列表引用没有问题,处理器启动的任务仍在修复引用锁定时的引用类型列表元素。

列表的元素仍然可以从处理器的任务中更改,并且在UI线程中访问它们将不是线程安全的。

如果我是正确的,一个明显的解决方案是让getter返回一个填充了列表元素的深层副本的新列表。

public List<T> GetList()
{
  lock(_listLock)
  {
    return _list.Select(t => t.Clone()).ToList();
  }
}

你还能做什么?

2 个答案:

答案 0 :(得分:2)

按照您的方式使用锁,无法确保线程安全。

如果您正在尝试线程安全,请考虑使用.NET CLR中提供的thread safe collections之一。

您会注意到没有线程安全的IList。 Here's why。线程安全列表的概念没有多大意义。但是,通过一些细微的设计更改,您可以轻松使用ConcurrentDictionaryConcurrentBag等内容。

答案 1 :(得分:0)

你的GetList()并没有按照你的想法行事:

public List<T> GetList()
  {
    lock(_listLock)
    {
      return _list;
    }
  }

由于GetList()只返回对_list的引用,因此lock()不会阻止2个线程同时获取对_list的引用,这无论如何都不是问题。

问题是您正在将对列表对象的引用传递回UI线程,并且引用指向的列表中的元素可能随时更改,而UI线程在元素上迭代时会发生事件,这不好。

您可以将锁定对象公开给UI线程,但这意味着您的UI需要在更新列表时阻止,这通常是不受欢迎的(阻止UI线程会降低用户体验)。

根据您的UI对列表的处理方式,最好采用列表的不可变快照并将其返回到UI。

如果您希望将UI与基础列表数据的更改保持同步,那么您采用的方法实际上取决于UI技术。