[C#.NET][TPL] Use Parallel Stacks Debug Windows,Find Out Multiple Thread of DeadLock
在開發多執行緒,常常會發生腦殘寫出死結 DeadLock,在使用傳統的 System.Threading 命名空間的執行緒類別,不太好觀察死結狀況
以下程式碼是用 ThreadPool 類別實現,為了確保執行序都執行完畢,於是我使用了WaitHandle 來等待,請參考以下:
[Thread] 執行緒的順序啟動 - WaitHandle.WaitAll 方法
但很不幸的以下程式碼永遠沒有結束的一天,因為它進入了死結,兩條執行緒在互等對方完成工作,這是一個很典型的死結寫法,這是我參考之前寫法改裝的
private void DoWork8() { WaitHandle[] waitHandles = new WaitHandle[] { new AutoResetEvent(false), new AutoResetEvent(false) }; ThreadPool.QueueUserWorkItem(_ => { ThreadPool.QueueUserWorkItem(state => { var reset = (AutoResetEvent)state; Thread t1 = Thread.CurrentThread; lock (_LockA) { Console.WriteLine("Thread[{0}]:Enter t1, _LockA be Locked ,State:{1}", t1.ManagedThreadId, t1.ThreadState); lock (_LockB) { Console.WriteLine("Thread[{0}]:Enter t1 ,_LockB be Locked ,State:{1}", t1.ManagedThreadId, t1.ThreadState); reset.Set(); } Console.WriteLine("Thread[{0}]:Leave t1 ,State:{1}", t1.ManagedThreadId, t1.ThreadState); } }, waitHandles[0]); ThreadPool.QueueUserWorkItem(state => { var reset = (AutoResetEvent)state; Thread t2 = Thread.CurrentThread; lock (_LockB) { Console.WriteLine("Thread[{0}]:Enter t2, _LockB be Locked ,State:{1}", t2.ManagedThreadId, t2.ThreadState); lock (_LockA) { Console.WriteLine("Thread[{0}]:Enter t2, _LockA be Locked ,State:{1}", t2.ManagedThreadId, t2.ThreadState); reset.Set(); } Console.WriteLine("Thread[{0}]:Leave t2 ,State:{1}", t2.ManagedThreadId, t2.ThreadState); } reset.Set(); }, waitHandles[1]); WaitHandle.WaitAll(waitHandles); }); }
當程式運行一段時間後,明明是很簡單的程式碼怎麼跑那麼久,按下暫停,如下圖:
按下暫停後可以看到執行緒的狀態,可以看到 Parallel Stacks 沒有任何的資料。
點選匿名方法後,發現執行緒都停在 lock 語句,不會往下執行。
現在我們改用 TPL 來處理,觀察會有什麼結果
private void DoWork9() { Task.Run(() => { Task t1 = new Task(() => { Thread t = Thread.CurrentThread; lock (_LockA) { Console.WriteLine("Thread[{0}]:Enter t1, _LockA be Locked ,State:{1}", t.ManagedThreadId, t.ThreadState); lock (_LockB) { Console.WriteLine("Thread[{0}]:Enter t1 ,_LockB be Locked ,State:{1}", t.ManagedThreadId, t.ThreadState); } Console.WriteLine("Thread[{0}]:Leave t1 ,State:{1}", t.ManagedThreadId, t.ThreadState); } }); Task t2 = new Task(() => { Thread t = Thread.CurrentThread; lock (_LockB) { Console.WriteLine("Thread[{0}]:Enter t2, _LockB be Locked ,State:{1}", t.ManagedThreadId, t.ThreadState); lock (_LockA) { Console.WriteLine("Thread[{0}]:Enter t2, _LockA be Locked ,State:{1}", t.ManagedThreadId, t.ThreadState); } Console.WriteLine("Thread[{0}]:Leave t2 ,State:{1}", t.ManagedThreadId, t.ThreadState); } }); t1.Start(); t2.Start(); Task.WaitAll(t1, t2); }); }
我一樣讓程式碼運行一斷時間,然後再按下暫停,這時 Parallel Stacks 視窗立即就顯示資料,告訴我們 Task 進入死結了,如下圖:
另外,你是否有看出用TPL寫的程式碼比較容易懂呢,TPL 不用處理 AutoResetEvent.Set 方法,整個程式碼看上去就乾淨多了。
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET