[C#.NET][TPL] 善用 Parallel Stacks 除錯視窗,找出執行緒死結 DeadLock

[C#.NET][TPL] Use Parallel Stacks Debug Windows,Find Out Multiple Thread of DeadLock

在開發多執行緒,常常會發生腦殘寫出死結 DeadLock,在使用傳統的 System.Threading 命名空間的執行緒類別,不太好觀察死結狀況

以下程式碼是用 ThreadPool 類別實現,為了確保執行序都執行完畢,於是我使用了WaitHandle 來等待,請參考以下:

[Thread] 執行緒的順序啟動 - WaitHandle.WaitAll 方法

 

但很不幸的以下程式碼永遠沒有結束的一天,因為它進入了死結,兩條執行緒在互等對方完成工作,這是一個很典型的死結寫法,這是我參考之前寫法改裝的

[Thread] 執行緒的互鎖與死鎖


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

 

當程式運行一段時間後,明明是很簡單的程式碼怎麼跑那麼久,按下暫停,如下圖:

image

 

按下暫停後可以看到執行緒的狀態,可以看到 Parallel Stacks 沒有任何的資料。

image

 

點選匿名方法後,發現執行緒都停在 lock 語句,不會往下執行。

image

 


現在我們改用 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 進入死結了,如下圖:

image

另外,你是否有看出用TPL寫的程式碼比較容易懂呢,TPL 不用處理 AutoResetEvent.Set 方法,整個程式碼看上去就乾淨多了。

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo