2016-07-05 3 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ループと同じロックを使用するため、反復処理中はコレクションを変更できません。

何が起きているのか分かっていれば、Start(5000)が呼び出されます。リストに何もないので、Thread.Sleep()にスキップします。バックグラウンドプロセスはジョブをリストに追加します。 whileループはforeachループに戻り、ロックを適用します。リストを反復処理する間、コレクションに追加しようとする他のスレッドは反復が完了するまで待機します。反復処理が完了すると、各スレッドはジョブを追加します。ジョブを追加しようとするスレッドが多数ある場合でも、ロックによって競合条件は発生しません。

実際には、このスレッドがスリープしている間に追加されたジョブはすべて正常に追加されます。このリストが反復処理されている間に追加されたジョブは、ロックされていても反復処理が完了するまで待機しません。

なぜこのエラーを防ぐロックはありませんか?

EDIT:ロック内の新しいリストにコピーすると、エラーが解消されます。

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)(これはスレッドセーフです)他のスレッドがアイテムを追加しようとしている間にリストとブロックを行う? –

答えて

3

問題は@Philipeによって発見されました。あなたはロック外のリストを変更しています。ロックによって保護されているすべての変更呼び出しを持つ必要があります。

単純化するために、完了していないジョブの新しいリストを計算して、ロック内の現在のジョブリストと入れ替えることができます。

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

このソリューションの逆をベースにしたソリューションで質問を編集しました。私はリストからそれらを取り除くことを超えて完了した仕事について仕事をする必要があったが、それはこの例には含まれていない。 – Adam

+0

これは、コレクションの新しいインスタンス(参照)を作成します。それが望ましいとは確信していません。 –

+0

プライベートメンバーなので安全だと思います。 –

2

_runningJobs.Remove(job);への電話はロックされていません。コレクションはそれを認識することができず、潜在的にロックされているアイテムをアイテムから削除しています(AddJob)。ロック内のジョブの削除を移動するか、それを1つにまとめると問題が解決します。

+0

thread.Sleep()を除いてすべてをロックに移動し、同じエラーが発生しました – Adam

+1

@Adamどのようなエラーがありましたか?期待どおりに動作していないものを正確に記述できますか? – Eli

+1

私は彼が質問を編集すべきだとは思わない。彼はこの答えをテストし、うまくいかないと言っています。答えは私にはうまく表示され、何が機能していないのかを彼に説明したいが、元の質問が立っている。 – Eli

関連する問題