2016-09-14 40 views
0

我有一個很大程度上基於https://stackoverflow.com/a/1656662/782181生產者/消費者代碼的C#線程池類。注:我這樣做,而不是使用BlockingCollection,因爲我堅持使用.NET2.0!生產者/消費者線程池w /主線程支持 - 不常發生死鎖?

我在可以從主線程調用的類中添加了一個函數,以允許主線程執行一些工作。我的想法是,在某些時候,主線程等待工作完成,但不用等待,我也可以讓主線程完成一些工作來加快速度。

下面是類的精簡版本,以證明:

public static class SGThreadPool 
{ 
    // Shared object to lock access to the queue between threads. 
    private static object locker = new object(); 

    // The various threads that are doing our work. 
    private static List<Thread> workers = null; 

    // A queue of tasks to be completed by the workers. 
    private static Queue<object> taskQueue = new Queue<object>(); 
    private static Queue<WaitCallback> taskCallbacks = new Queue<WaitCallback>(); 

    //OMMITTED: Init function (starts threads) 

    // Enqueues a task for a thread to do. 
    public static void EnqueueTask(WaitCallback callback, object context) 
    { 
     lock(locker) 
     { 
      taskQueue.Enqueue(context); 
      taskCallbacks.Enqueue(callback); 
      Monitor.PulseAll(locker); //Q: should I just use 'Pulse' here? 
     } 
    } 

    // Can be called from main thread to have it "help out" with tasks. 
    public static bool PerformTask() 
    { 
     WaitCallback taskCallback = null; 
     object task = null; 
     lock(locker) 
     { 
      if(taskQueue.Count > 0) 
      { 
       task = taskQueue.Dequeue(); 
      } 
      if(taskCallbacks.Count > 0) 
      { 
       taskCallback = taskCallbacks.Dequeue(); 
      } 
     } 

     // No task means no work, return false. 
     if(task == null || taskCallback == null) { return false; } 

     // Do the work! 
     taskCallback(task); 
     return true; 
    } 

    private static void Consume() 
    { 
     while(true) 
     { 
      WaitCallback taskCallback = null; 
      object task = null; 
      lock(locker) 
      { 
       // While no tasks in the queue, wait. 
       while(taskQueue.Count == 0) 
       { 
        Monitor.Wait(locker); 
       } 

       // Get a task. 
       task = taskQueue.Dequeue(); 
       taskCallback = taskCallbacks.Dequeue(); 
      } 

      // Null task signals an exit. 
      if(task == null || taskCallback == null) { return; } 

      // Call consume callback with task as context. 
      taskCallback(task); 
     } 
    } 
} 

基本上,我可以排隊到通過後臺線程來執行多項任務。但主線程也可以通過調用PerformTask()來執行任務。

我遇到了一個偶然的問題,主線程會嘗試在PerformTask()中「鎖定」,但鎖已經被佔用。主線程在等待,但由於某種原因,鎖定不可用。

代碼中沒有任何東西出現在導致死鎖的問題上 - 我希望別人能夠發現問題。我已經看了幾個小時,我不確定爲什麼主線程會卡在PerformTask()中的「lock()」調用中。似乎沒有其他線程會無限期地鎖定鎖定?允許主線程以這種方式與池交互是不是一個好主意?

+0

也許,你想嘗試'BlockingCollection'而不是隊列+鎖 –

+0

@ L.B,我真的認爲這將是偉大的,但我在Unity工作,它暫時停留在.NET2.0上!我會更新問題以表明這一點。 – kromenak

回答

0

嗯,所以,雖然我仍然想知道爲什麼上面的代碼可能會在某些情況下發生死鎖,我想我已經找到了一個解決方法,可以做到這一點。

如果主線程要在這裏工作,我想確保主線程不會長時間被阻塞。畢竟,一個通用的開發規則:不要阻塞主線程!

所以,我試圖解決的辦法是直接使用Monitor.TryEnter,而不是對主線程使用lock()。這允許我指定主線程願意等待鎖的時間超時。

public static bool PerformTask() 
{ 
    WaitCallback taskCallback = null; 
    object task = null; 

    // Use TryEnter, rather than "lock" because 
    // it allows us to specify a timeout as a failsafe. 
    if(Monitor.TryEnter(locker, 500)) 
    { 
     try 
     { 
      // Pull a task from the queue. 
      if(taskQueue.Count > 0) 
      { 
       task = taskQueue.Dequeue(); 
      } 
      if(taskCallbacks.Count > 0) 
      { 
       taskCallback = taskCallbacks.Dequeue(); 
      } 
     } 
     finally 
     { 
      Monitor.Exit(locker); 
     } 
    } 

    // No task means no work, return false. 
    if(task == null || taskCallback == null) { return false; } 

    // Do the work! 
    taskCallback(task); 
    return true; 
} 

在這段代碼中,線程將等待獲取長達500ms的鎖。如果因爲任何原因不能完成任何任務,但至少它不會卡住。不要把主線放在無限期等待的位置,這似乎是個好主意。

我相信,當你使用lock()時,編譯器會生成類似的代碼,所以我不認爲這個解決方案會有任何性能問題。

+1

你的方法是正確的,但我只想指出你的代碼是否出隊 - 我建議你在這兩個隊列中聲明計數的相等性。不平等似乎不真實,但我認爲這應該完成。只是爲了檢查你的不變式。 – VMAtm