C# MultiThreading - List<Object> 在另一个线程处理列表时从一个线程锁定列表
C# MultiThreading - List<Object> Locking a list from a thread while another thread is working on the list
我的程序有 2 个主线程:
Thread checkForNewItemsThread
添加新对象到 volatile List<Object>
Thread workOnListThread
在 List<Object>
上工作,并为列表中需要工作的每个项目创建另一个新的 Thread doWorkThread
。
我 运行 在 workOnListThread
期间出现 ArgumentNull 异常,因为(我认为正在发生的事情)要么:
- 我的
checkForNewItemsThread
正在添加到列表中,而 workOnListThread
正在遍历列表。
- 我的
doWorkThread
正在处理完该对象后从列表中删除该项目。
我认为在这里正确的做法是在 workOnListThread
处理列表时锁定 List<Object>
,但是,我很难确定正确的方法在 List<Object>
上放置锁的位置,我认为这应该是正确的,但是可以使用一些指导。
volatile List<Object> List1 = new List<Object>();
private static readonly object _lock = new object();
Thread checkForNewItemsThread;
Thread workOnListThread;
Thread doWorkThread;
public void OnTimerCheckNewItems(object sender, ElapsedEventArgs args)
{
List<Object> List1Temp = new List<Object>();
List1Temp = getList();
addNewItems(List1Temp);
}
private void addNewItems(List<Object> list)
{
foreach (Object obj in list)
{
if (!List1.Any(o => o.ID == obj.ID)) //check if object already exists
{
List1.Add(obj);
}
}
}
private void workOnList()
{
while (true) //loop forever
{
while (List1.Count != 0) //while items exist
{
lock (_lock) //lock while iterating through list
{
try
{
for (int i = 0; i < List1.Count; i++)
{
if (List1[i].Status == Status.ready && List1[i] != null)
{
doWorkThread = new Thread(() => doWork(List1[i]));
doWorkThread.Start();
Thread.Sleep(3000); // wait for process to start
}
}
}
catch (Exception e)
{
Console.WriteLine(DateTime.Now.ToString(), " : ", e);
Log(e.ToString());
}
}
}
}
}
private void doWork(Object obj)
{
lock (_lock) //lock during update to status
{
int i = List.IndexOf(obj);
List1[i].Status = Status.pending;
}
//work done here
List1.Remove(alert);
}
List<T>
class 不是线程安全的,所以如果你想从多个线程同时访问它,你必须保护 every 访问它使用 lock
。放置锁的正确位置是 每个 位置,你是 reading/writing 列表的 属性,或调用它的任何方法。否则,您从 class 的制造商那里得到的唯一保证是没有任何保证。 class 的正式行为变成了 "undefined",如果你试图制作一个在结果正确性方面应该可靠的程序,这会非常可怕。
由于锁定会产生争用,因此您必须尽可能短暂地持有锁。不要在锁内做任何与 List
本身无关的事情。例如,如果您需要枚举列表的元素并对每个元素做一些事情,那么最好获取锁,获取列表的本地快照,释放锁,最后枚举本地快照。
手动同步 List
的另一种方法是使用其中一个可用的 concurrent collections, like the ConcurrentQueue
or the ConcurrentDictionary
。这些 classes 提供了专门的 API,可以非常有效地执行原子操作,但通常使用起来很麻烦,而且不如手动同步灵活。
我的程序有 2 个主线程:
Thread checkForNewItemsThread
添加新对象到volatile List<Object>
Thread workOnListThread
在List<Object>
上工作,并为列表中需要工作的每个项目创建另一个新的Thread doWorkThread
。
我 运行 在 workOnListThread
期间出现 ArgumentNull 异常,因为(我认为正在发生的事情)要么:
- 我的
checkForNewItemsThread
正在添加到列表中,而workOnListThread
正在遍历列表。 - 我的
doWorkThread
正在处理完该对象后从列表中删除该项目。
我认为在这里正确的做法是在 workOnListThread
处理列表时锁定 List<Object>
,但是,我很难确定正确的方法在 List<Object>
上放置锁的位置,我认为这应该是正确的,但是可以使用一些指导。
volatile List<Object> List1 = new List<Object>();
private static readonly object _lock = new object();
Thread checkForNewItemsThread;
Thread workOnListThread;
Thread doWorkThread;
public void OnTimerCheckNewItems(object sender, ElapsedEventArgs args)
{
List<Object> List1Temp = new List<Object>();
List1Temp = getList();
addNewItems(List1Temp);
}
private void addNewItems(List<Object> list)
{
foreach (Object obj in list)
{
if (!List1.Any(o => o.ID == obj.ID)) //check if object already exists
{
List1.Add(obj);
}
}
}
private void workOnList()
{
while (true) //loop forever
{
while (List1.Count != 0) //while items exist
{
lock (_lock) //lock while iterating through list
{
try
{
for (int i = 0; i < List1.Count; i++)
{
if (List1[i].Status == Status.ready && List1[i] != null)
{
doWorkThread = new Thread(() => doWork(List1[i]));
doWorkThread.Start();
Thread.Sleep(3000); // wait for process to start
}
}
}
catch (Exception e)
{
Console.WriteLine(DateTime.Now.ToString(), " : ", e);
Log(e.ToString());
}
}
}
}
}
private void doWork(Object obj)
{
lock (_lock) //lock during update to status
{
int i = List.IndexOf(obj);
List1[i].Status = Status.pending;
}
//work done here
List1.Remove(alert);
}
List<T>
class 不是线程安全的,所以如果你想从多个线程同时访问它,你必须保护 every 访问它使用 lock
。放置锁的正确位置是 每个 位置,你是 reading/writing 列表的 属性,或调用它的任何方法。否则,您从 class 的制造商那里得到的唯一保证是没有任何保证。 class 的正式行为变成了 "undefined",如果你试图制作一个在结果正确性方面应该可靠的程序,这会非常可怕。
由于锁定会产生争用,因此您必须尽可能短暂地持有锁。不要在锁内做任何与 List
本身无关的事情。例如,如果您需要枚举列表的元素并对每个元素做一些事情,那么最好获取锁,获取列表的本地快照,释放锁,最后枚举本地快照。
手动同步 List
的另一种方法是使用其中一个可用的 concurrent collections, like the ConcurrentQueue
or the ConcurrentDictionary
。这些 classes 提供了专门的 API,可以非常有效地执行原子操作,但通常使用起来很麻烦,而且不如手动同步灵活。