此問題已被多次詢問,但這是一個特例。使用鎖定不會阻止收集被修改;枚舉操作可能不會執行
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循環相同的鎖,因此在迭代過程中它不應該修改集合。
我對應該發生的事情的理解是開始(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()在鎖之外? – Eli
看起來你正在使用一個列表來模擬一個隊列。爲什麼不在一個'while'循環中使用['ConcurrentQueue'](https://msdn.microsoft.com/en-us/library/dd267265.aspx)(它是_already_線程安全的),而不是試圖枚舉列表並阻止其他線程嘗試向其中添加項目? –