2016-07-05 19 views
0

此問題已被多次詢問,但這是一個特例。使用鎖定不會阻止收集被修改;枚舉操作可能不會執行

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); 
     } 
    } 
} 
+4

請注意,您的最終'foreach'正在修改鎖外的集合。 –

+0

你如何知道在迭代過程中添加了作業?你有這方面的證據嗎?爲什麼Remove()在鎖之外? – Eli

+0

看起來你正在使用一個列表來模擬一個隊列。爲什麼不在一個'while'循環中使用['ConcurrentQueue'](https://msdn.microsoft.com/en-us/library/dd267265.aspx)(它是_already_線程安全的),而不是試圖枚舉列表並阻止其他線程嘗試向其中添加項目? –

回答

3

問題被@Philipe發現,你正在修改鎖外的列表。你應該讓所有的修改調用受鎖保護。

爲簡化起見,您可以計算未完成作業的新列表並與鎖內當前作業列表進行交換。一些沿線的:

lock(myLock) { 
    var newRunningJobs = _runningJob.Where(j => !Job.IsComplete(j)).ToList(); 
    _runningJob = newRunningJobs; 
} 
+0

基於此解決方案的反向解決方案編輯該問題。除了將它們從列表中刪除之外,我需要完成已完成作業的工作,但爲了簡單起見,本示例中沒有包含這些內容。 – Adam

+0

這將創建一個新的集合實例(引用)。不確定這是可取的。 –

+0

我想這是安全的,因爲它是私人會員 –

2

您致電_runningJobs.Remove(job);未鎖定。集合無法意識到它,並且您正在從其中刪除項目,同時可能會在AddJob中鎖定項目。將作業移入鎖中或將其包裝在一箇中,它應該可以解決您的問題。

+0

我除了thread.Sleep()並且得到了相同的錯誤之外,我把所有的東西都移到了鎖裏 – Adam

+1

@Adam你得到了什麼錯誤?你能準確地描述什麼是不按預期工作嗎? – Eli

+1

我不認爲他應該編輯這個問題。他測試了這個答案,並表示它不起作用。答案似乎對我很好,並希望他解釋什麼不起作用,但原始問題的立場。 – Eli

相關問題