c#: 線程狀態和管理之線程的休眠、掛起和中斷

環境:

  • window 10
  • .NetFramework 4.7
  • vs2019 16.4.5

一、線程的狀態

線程的狀態可以從枚舉ThreadState中查看到:

public enum ThreadState
{
    Running = 0x0,
    StopRequested = 0x1,
    SuspendRequested = 0x2,
    Background = 0x4,
    Unstarted = 0x8,
    Stopped = 0x10,
    WaitSleepJoin = 0x20,
    Suspended = 0x40,
    AbortRequested = 0x80,
    Aborted = 0x100
}

我們先拋開Background不談,說一下其他的九種狀態:

  • Unstarted :
    當我們var thread = new Thread(()=>{})執行後,這個線程就被創建了,此時它並不會被cpu執行,它的狀態爲:Unstarted
  • Running :
    當我們調用thread.Start()方法後,這個線程就安排給cpu調用了,此時它的狀態爲Running
  • Stopped、StopRequested :
    當我們創建的線程正常執行後,它沒代碼執行了,也就是死掉了,此時它的狀態爲Stopped ,注意:這裏說的是正常執行完畢,並不是被強制終止(thead.Abort())。至於StopRequested,從名字可以看出它是處於Stopped狀態之前的,可能是速度太快,我調試的時候並沒有捕獲到。最後,線程結束後不能重新發起
  • WaitSleepJoin :
    這種狀態也稱之爲休眠狀態,由thread.Join()或Thread.Sleep()引發,前者是主線程調用子線程的Join方法導致主線程阻塞(直到子線程執行完畢才能解除阻塞)並切換到WaitSleepJoin狀態,後者是子線程內部調用Sleep方法導致子線程休眠並切換到WaitSleepJoin狀態。從這個方法的名字中也可以看到它主要是由Join和Sleep方法引起的線程休眠(程序鎖也會導致休眠),而解除這種狀態的方法是thread.Interrupt()。注意:thread.Interrupt()會導致線程在阻塞處拋出異常,注意捕捉。
  • Suspended、SuspendRequested:(掛起操作不被建議使用,標記爲廢棄的api,.netcore中不支持Suspend/Resume方法的調用
    線程被掛起時的狀態,由thread.Suspend()引發。注意:進程的掛起和休眠不是一個意思,線程的休眠必須指定休眠的時間,時間一到就自動解除休眠,而掛起則不然,它必須被其他線程解除掛起纔行,也可以這樣說:線程的休眠是線程本身根據需要引發的,而掛起則是由管理者決定的。當一個線程處於WaitSleepJoin狀態時管理者仍然可以將這個線程標記爲掛起,只不過這個由於線程已經自己休眠了,所以就暫時安排個SuspendRequested狀態,等它的休眠期已過就立刻打上Suspend狀態。還有一個重要的地方,線程被掛起時會拋出異常,如果沒有處理線程就會死掉,如果處理了,代碼的執行位置就會改變,這一點和休眠是很不同的。最後,處於Suspend狀態的線程只能由管理者調用Resume()方法解除掛起
  • Aborted、AbortRequested(結束線程最好的方法是通過信號燈讓線程自己中斷,而不是強制AbortC# Thread.Abort方法真的讓線程停止了嗎?,.netcore中不支持Abort方法的調用)
    線程被終止狀態,這個和Stopped不同,Stopped是正常執行完畢,而Aborted是被強制終止並拋出異常。

在這裏插入圖片描述
注意:
不要使用Suspend和Resume方法來同步線程的活動。當你Suspend線程時,您無法知道線程正在執行什麼代碼。如果在安全權限評估期間線程持有鎖時掛起線程,則AppDomain中的其他線程可能會被阻塞。如果線程在執行類構造函數時Suspend,則試圖使用該類的AppDomain中的其他線程將被阻塞。死鎖很容易發生。

二、測試代碼

2.1 測試Join()方法阻塞主線程

using System;
using System.Collections;
using System.Data;
using System.IO;
using System.Threading;
namespace TestDI
{
    class Program
    {
        public static void Main(string[] args)
        {
            Test6();
            Console.WriteLine("ok");
            Console.ReadLine();
        }

        private static void Test6()
        {
            var threadMain = Thread.CurrentThread;
            var thread = new Thread(() =>
            {
                Thread.Sleep(2000);
                for (int i = 0; i < 10000; i++)
                {
                    File.AppendAllText("d:\\temp.txt", i + "\r\n");
                }
                //由於這個線程被主線程執行thread.Join(),從而導致主線程被阻塞,所以主線程狀態爲:WaitSleepJoin
                Console.WriteLine("主線程狀態:" + threadMain.ThreadState);
            });
            //線程thread剛被創建,還未啓動,狀態爲:Unstarted
            Console.WriteLine(thread.ThreadState);
            thread.Start();
            //線程thread啓動後,線程的狀態變爲:Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(1000);
            //線程thread此時執行到Thread.Sleep(2000);,狀態爲:WaitSleepJoin
            Console.WriteLine(thread.ThreadState);
            thread.Join();
            //線程被執行了Join導致此處代碼被阻塞,這裏獲取的狀態肯定是Stopped
            Console.WriteLine(thread.ThreadState);
            //主線程狀態也從WaitSleepJoin改爲Running
            Console.WriteLine("主線程:" + threadMain.ThreadState);
        }

    }
}

輸出效果:
在這裏插入圖片描述

2.2 測試Abort()方法終止線程

.netcore不支持Abort,而且線程的正確終止方法應該是通過信號燈讓線程本身結束,而不是強制結束,因爲你無法預測線程正在做什麼!

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Test5();
            Console.WriteLine("ok");
            Console.ReadLine();
        }

        private static void Test5()
        {
            var thread = new Thread(() =>
            {
                try
                {
                    Thread.Sleep(2000);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("異常:" + ex.Message);
                }
                for (int i = 0; i < 10000; i++)
                {
                    File.AppendAllText("d:\\temp.txt", i + "\r\n");
                }
            });
            //線程thread剛被創建,還未啓動,狀態爲:Unstarted
            Console.WriteLine(thread.ThreadState);
            thread.Start();
            //線程thread啓動後,線程的狀態變爲:Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(1000);
            //線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
            Console.WriteLine(thread.ThreadState);
            thread.Abort();
            Thread.Sleep(500);
            //線程thread已經Aborted了
            Console.WriteLine(thread.ThreadState);
        }
    }
}

輸出效果:
在這裏插入圖片描述

2.3 測試線程的掛起和休眠的疊加狀態: 先從休眠中喚醒再解除掛起

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Test4();
            Console.WriteLine("ok");
            Console.ReadLine();
        }

        private static void Test4()
        {
            var thread = new Thread(() =>
            {
                try
                {
                    Thread.Sleep(2000);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("異常:" + ex.Message);
                }
                for (int i = 0; i < 1000; i++)
                {
                    File.AppendAllText("d:\\temp.txt", i + "\r\n");
                }
            });
            //線程thread剛被創建,還未啓動,狀態爲:Unstarted
            Console.WriteLine(thread.ThreadState);
            thread.Start();
            //線程thread啓動後,線程的狀態變爲:Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(1000);
            //線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
            Console.WriteLine(thread.ThreadState);
            thread.Suspend();
            Thread.Sleep(500);
            //線程thread被執行Suspend(),狀態爲SuspendRequested, WaitSleepJoin,並引發異常
            Console.WriteLine(thread.ThreadState);
            if (thread.ThreadState == (ThreadState.SuspendRequested | ThreadState.WaitSleepJoin))
            {
                Console.WriteLine("ThreadState.SuspendRequested | ThreadState.WaitSleepJoin\t" + true);
            }
            Thread.Sleep(1500);
            //線程thread此時已從Sleep中甦醒,狀態爲Suspend
            Console.WriteLine(thread.ThreadState);
            thread.Resume();
            Thread.Sleep(500);
            //線程thread被執行Resume(),狀態爲Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(10000);
            //線程thread已經執行完畢,狀態爲Stopped
            Console.WriteLine(thread.ThreadState);
        }
    }
}

輸出效果:
在這裏插入圖片描述

2.4 測試線程的掛起和休眠的疊加狀態: 先解除掛起再從休眠中喚醒

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Test3();
            Console.WriteLine("ok");
            Console.ReadLine();
        }

        /// <summary>
        /// 測試線程狀態Unstarted->Running->WaitSleepJoin->SuspendRequested, WaitSleepJoin->WaitSleepJoin->Running->Stopped
        /// 線程先睡眠再掛起,然後解除掛起,然後自動喚醒
        /// .netcore不支持Suspend/Resume,而且這兩個方法已經標記爲廢棄的api
        /// </summary>
        private static void Test3()
        {
            var thread = new Thread(() =>
            {
                try
                {
                    Thread.Sleep(2000);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("異常:" + ex.Message);
                }
                for (int i = 0; i < 1000; i++)
                {
                    File.AppendAllText("d:\\temp.txt", i + "\r\n");
                }
            });
            //線程thread剛被創建,還未啓動,狀態爲:Unstarted
            Console.WriteLine(thread.ThreadState);
            thread.Start();
            //線程thread啓動後,線程的狀態變爲:Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(1000);
            //線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
            Console.WriteLine(thread.ThreadState);
            thread.Suspend();
            Thread.Sleep(500);
            //線程thread被執行Suspend(),狀態爲SuspendRequested, WaitSleepJoin
            Console.WriteLine(thread.ThreadState);
            thread.Resume();
            Thread.Sleep(500);
            //線程thread被執行Resume(),狀態爲WaitSleepJoin
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(2000);
            //線程thread已經從Sleep中甦醒,狀態爲Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(8000);
            //線程thread已經執行完畢,狀態爲Stopped
            Console.WriteLine(thread.ThreadState);
        }
    }
}

輸出效果:
在這裏插入圖片描述

2.5 測試線程休眠後使用Interrupt方法喚醒

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Test2();
            Console.WriteLine("ok");
            Console.ReadLine();
        }

        /// <summary>
        /// 測試線程狀態Unstarted->Running->WaitSleepJoin->Running->Stopped
        /// 休眠後使用Interrupt方法喚醒
        /// </summary>
        private static void Test2()
        {
            var thread = new Thread(() =>
            {
                try
                {
                    Thread.Sleep(2000);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                for (int i = 0; i < 1000; i++)
                {
                    File.AppendAllText("d:\\temp.txt", i + "\r\n");
                }
            });
            //線程thread剛被創建,還未啓動,狀態爲:Unstarted
            Console.WriteLine(thread.ThreadState);
            thread.Start();
            //線程thread啓動後,線程的狀態變爲:Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(1000);
            //線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
            Console.WriteLine(thread.ThreadState);
            thread.Interrupt();
            Thread.Sleep(100);
            //線程thread被執行Interrupt(),狀態爲Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(10 * 1000);
            //線程thread已經執行完畢,狀態爲Stopped
            Console.WriteLine(thread.ThreadState);
        }
    }
}

輸出效果:
在這裏插入圖片描述

2.6 測試線程休眠後自動喚醒

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Test1();
            Console.WriteLine("ok");
            Console.ReadLine();
        }

        /// <summary>
        /// 測試線程狀態Unstarted->Running->WaitSleepJoin->Running->Stopped
        /// 休眠到期後自動喚醒
        /// </summary>
        private static void Test1()
        {
            var thread = new Thread(() =>
            {
                Thread.Sleep(2000);
                for (int i = 0; i < 1000; i++)
                {
                    File.AppendAllText("d:\\temp.txt", i + "\r\n");
                }
            });
            //線程thread剛被創建,還未啓動,狀態爲:Unstarted
            Console.WriteLine(thread.ThreadState);
            thread.Start();
            //線程thread啓動後,線程的狀態變爲:Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(1000);
            //線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(1500);
            //線程thread此時正在運行,狀態爲Running
            Console.WriteLine(thread.ThreadState);
            Thread.Sleep(8000);
            //線程thread此時已經運行完畢,狀態爲Stopped
            Console.WriteLine(thread.ThreadState);
        }
    }
}

輸出效果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章