【发布时间】:2016-11-07 09:12:54
【问题描述】:
这个问题已经被问过很多次了,但这是一个特例。
public class JobStatusMonitor
{
private static List<Job> _runningJobs = new List<Job>();
private static object myLock = new object();
public static void AddJob(GPSJob input)
{
lock (myLock)
_runningJobs.Add(input);
}
public static void Start(int pollInterval)
{
while (true)
{
var removeJobs = new List<GPSJob>();
lock (myLock)
{
foreach (var job in _runningJobs)
{
if (job.IsComplete())
{
removeJobs.Add(job);
}
}
}
foreach (var job in removeJobs)
{
_runningJobs.Remove(job);
}
System.Threading.Thread.Sleep(pollInterval);
}
}
}
列表 _runningJobs 是私有的,因此除非它使用 AddJob 方法,否则此类之外的任何内容都不能修改它。 AddJob 方法使用与 foreach 循环相同的锁,因此它应该无法在迭代时修改集合。
我对应该发生的事情的理解是 Start(5000) 被调用,列表中没有任何内容,因此它跳到 Thread.Sleep()。后台进程将作业添加到列表中。 while 循环返回到 foreach 循环并应用锁。在迭代列表时,任何其他尝试添加到集合的线程都会等待迭代完成。一旦迭代完成,这些线程中的每一个都会添加它们的作业,即使有许多线程试图添加作业,锁也不会导致竞争条件。
实际发生的情况是,在此线程休眠时添加的任何作业都已成功添加。尽管有锁定,但在此列表迭代时添加的作业不会等待迭代完成。
为什么锁不能防止这个错误?
编辑:复制到锁内的新列表可消除错误。
public class JobStatusMonitor
{
private static List<Job> _runningJobs = new List<Job>();
private static object myLock = new object();
public static void AddJob(GPSJob input)
{
lock (myLock)
{
_runningJobs.Add(input);
}
}
public static void Start(int pollInterval)
{
while (true)
{
lock (myLock)
{
var completeJobs = _runningJobs.Where(job => job.IsComplete()).ToList();
foreach (var job in completeJobs)
{
_runningJobs.Remove(job);
job.TaskCompletionSource.SetResult(null);
}
}
System.Threading.Thread.Sleep(pollInterval);
}
}
}
【问题讨论】:
-
请注意,您的最终
foreach正在修改锁之外的集合。 -
如何知道在迭代过程中添加了作业?你有这方面的证据吗?为什么 Remove() 在锁之外?
-
您似乎正在使用列表来模拟队列。为什么不在
while循环中使用ConcurrentQueue(已经线程安全),而不是在其他线程尝试向其中添加项目时尝试枚举列表并阻塞?跨度> -
@DStanley 他可以删除列表中间的工作,如果他们在其他人之前完成。
-
你是从不同的线程调用
Start还是只调用一次?
标签: c# multithreading exception