据我了解,他们没有。请确认。
假设我有一个包含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();
}
}
你还能做什么?
答案 0 :(得分:2)
按照您的方式使用锁,无法确保线程安全。
如果您正在尝试线程安全,请考虑使用.NET CLR中提供的thread safe collections之一。
您会注意到没有线程安全的IList。 Here's why。线程安全列表的概念没有多大意义。但是,通过一些细微的设计更改,您可以轻松使用ConcurrentDictionary或ConcurrentBag等内容。
答案 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技术。