2016-11-24 23 views
0

我的理解是,他们没有。请确认。锁确保列表元素的线程安全吗?

假设我有一个包含列表的处理器对象<T>其中T是引用类型。

UI线程实例化处理器对象,并定期调用它来获取列表<T>。

处理器对象在构建时还会启动一个参考列表<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(...); 
    } 
    } 
} 

但即使名单<牛逼>被锁定在吸气剂,它没有问题返回列表参考,由处理器启动任务仍在修改引用类型列表中的元素,当它抓住了锁。

列表的元素仍然受到来自处理器的任务发生变化,并访问它们在UI线程不会是线程安全的。

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

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

你还能做什么?

+0

不是答案,只是调用'ToList()'创建列表的副本。 – stuartd

+0

您只能公开您使用的'List '中的方法,而不是整个列表。这样,您可以使用锁定来访问和修改项目。 –

+0

你是否清楚这样一个事实:在修改列表中的元素时,像这样的列表上锁定时不会提供踩踏安全性? – Enigmativity

回答

0

你的GetList()不会做你认为它的作用:

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

由于的GetList()只返回一个参考_List,锁()什么都不做,除了防止2个线程得到一个参考同时列出,这不是问题。

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

您可以将锁对象暴露给UI线程,但这意味着您的UI将需要在列表更新时阻塞,这通常是不合需要的(阻塞UI线程会降低用户体验)。

根据您的用户界面对列表所做的操作,最好为您的列表创建一个不可变的快照并将其返回给用户界面。

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

2

锁定,使用你的方式,不会确保线程安全。

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

你会注意到没有线程安全的IList。 Here's why。线程安全列表的概念没有多大意义。但是通过一些小的设计更改,您可以轻松使用类似ConcurrentDictionaryConcurrentBag的东西。