溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C#線程的創(chuàng)建和生命周期實(shí)例分析

發(fā)布時(shí)間:2022-02-14 09:30:13 來源:億速云 閱讀:144 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“C#線程的創(chuàng)建和生命周期實(shí)例分析”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

1,獲取當(dāng)前線程信息

Thread.CurrentThread 是一個(gè) 靜態(tài)的 Thread 類,Thread 的CurrentThread 屬性,可以獲取到當(dāng)前運(yùn)行線程的一些信息,其定義如下:

public static System.Threading.Thread CurrentThread { get; }

Thread 類有很多屬性和方法,這里就不列舉了,后面的學(xué)習(xí)會(huì)慢慢熟悉更多 API 和深入了解使用。

這里有一個(gè)簡單的示例:

        static void Main(string[] args)
        {
            Thread thread = new Thread(OneTest);
            thread.Name = "Test";
            thread.Start();
            Console.ReadKey();
        }

        public static void OneTest()
        {
            Thread thisTHread = Thread.CurrentThread;
            Console.WriteLine("線程標(biāo)識(shí):" + thisTHread.Name);
            Console.WriteLine("當(dāng)前地域:" + thisTHread.CurrentCulture.Name);  // 當(dāng)前地域
            Console.WriteLine("線程執(zhí)行狀態(tài):" + thisTHread.IsAlive);
            Console.WriteLine("是否為后臺(tái)線程:" + thisTHread.IsBackground);
            Console.WriteLine("是否為線程池線程"+thisTHread.IsThreadPoolThread);
        }

輸出

線程標(biāo)識(shí):Test
當(dāng)前地域:zh-CN
線程執(zhí)行狀態(tài):True
是否為后臺(tái)線程:False
是否為線程池線程False

2,管理線程狀態(tài)

一般認(rèn)為,線程有五種狀態(tài):

新建(new 對象) 、就緒(等待CPU調(diào)度)、運(yùn)行(CPU正在運(yùn)行)、阻塞(等待阻塞、同步阻塞等)、死亡(對象釋放)。

C#線程的創(chuàng)建和生命周期實(shí)例分析

理論的東西不說太多,直接擼代碼。

2.1 啟動(dòng)與參數(shù)傳遞

新建線程簡直滾瓜爛熟,無非 new 一下,然后 Start()。

Thread thread = new Thread();

Thread 的構(gòu)造函數(shù)有四個(gè):

public Thread(ParameterizedThreadStart start);

public Thread(ThreadStart start);

public Thread(ParameterizedThreadStart start, int maxStackSize);

public Thread(ThreadStart start, int maxStackSize);

我們以啟動(dòng)新的線程時(shí)傳遞參數(shù)來舉例,使用這四個(gè)構(gòu)造函數(shù)呢?

2.1.1 ParameterizedThreadStart

ParameterizedThreadStart 是一個(gè)委托,構(gòu)造函數(shù)傳遞的參數(shù)為需要執(zhí)行的方法,然后在 Start 方法中傳遞參數(shù)。

需要注意的是,傳遞的參數(shù)類型為 object,而且只能傳遞一個(gè)。

代碼示例如下:

        static void Main(string[] args)
        {
            string myParam = "abcdef";
            ParameterizedThreadStart parameterized = new ParameterizedThreadStart(OneTest);
            Thread thread = new Thread(parameterized);
            thread.Start(myParam);
            Console.ReadKey();
        }

        public static void OneTest(object obj)
        {
            string str = obj as string;
            if (string.IsNullOrEmpty(str))
                return;

            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
            Console.WriteLine(str);
        }
2.1.2 使用靜態(tài)變量或類成員變量

此種方法不需要作為參數(shù)傳遞,各個(gè)線程共享堆棧。

優(yōu)點(diǎn)是不需要裝箱拆箱,多線程可以共享空間;缺點(diǎn)是變量是大家都可以訪問,此種方式在多線程競價(jià)時(shí),可能會(huì)導(dǎo)致多種問題(可以加鎖解決)。

下面使用兩個(gè)變量實(shí)現(xiàn)數(shù)據(jù)傳遞:

    class Program
    {
        private string A = "成員變量";
        public static string B = "靜態(tài)變量";

        static void Main(string[] args)
        {
            // 創(chuàng)建一個(gè)類
            Program p = new Program();

            Thread thread1 = new Thread(p.OneTest1);
            thread1.Name = "Test1";
            thread1.Start();

            Thread thread2 = new Thread(OneTest2);
            thread2.Name = "Test2";
            thread2.Start();

            Console.ReadKey();
        }

        public void OneTest1()
        {
            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
            Console.WriteLine(A);       // 本身對象的其它成員
        }
        public static void OneTest2()
        {
            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
            Console.WriteLine(B);       // 全局靜態(tài)變量
        }
    }
2.1.3 委托與Lambda

原理是 Thread 的構(gòu)造函數(shù) public Thread(ThreadStart start);,ThreadStart 是一個(gè)委托,其定義如下

public delegate void ThreadStart();

使用委托的話,可以這樣寫

        static void Main(string[] args)
        {
            System.Threading.ThreadStart start = DelegateThread;

            Thread thread = new Thread(start);
            thread.Name = "Test";
            thread.Start();


            Console.ReadKey();
        }

        public static void DelegateThread()
        {
            OneTest("a", "b", 666, new Program());
        }
        public static void OneTest(string a, string b, int c, Program p)
        {
            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
        }

有那么一點(diǎn)點(diǎn)麻煩,不過我們可以使用 Lambda 快速實(shí)現(xiàn)。

使用 Lambda 示例如下:

        static void Main(string[] args)
        {
            Thread thread = new Thread(() =>
            {
                OneTest("a", "b", 666, new Program());
            });
            thread.Name = "Test";
            thread.Start();
            
            Console.ReadKey();
        }

        public static void OneTest(string a, string b, int c, Program p)
        {
            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
        }

提示:如果需要處理的算法比較簡單的話,可以直接寫進(jìn)委托中,不需要另外寫方法啦。

可以看到,C# 是多么的方便。

2.2 暫停與阻塞

Thread.Sleep() 方法可以將當(dāng)前線程掛起一段時(shí)間,Thread.Join() 方法可以阻塞當(dāng)前線程一直等待另一個(gè)線程運(yùn)行至結(jié)束。

在等待線程 Sleep() 或 Join() 的過程中,線程是阻塞的(Blocket)。

    阻塞的定義:當(dāng)線程由于特點(diǎn)原因暫停執(zhí)行,那么它就是阻塞的。 
    如果線程處于阻塞狀態(tài),線程就會(huì)交出他的 CPU 時(shí)間片,并且不會(huì)消耗 CPU 時(shí)間,直至阻塞結(jié)束。 
    阻塞會(huì)發(fā)生上下文切換。

代碼示例如下:

        static void Main(string[] args)
        {
            Thread thread = new Thread(OneTest);
            thread.Name = "小弟弟";

            Console.WriteLine($"{DateTime.Now}:大家在吃飯,吃完飯后要帶小弟弟逛街");
            Console.WriteLine("吃完飯了");
            Console.WriteLine($"{DateTime.Now}:小弟弟開始玩游戲");
            thread.Start();

            // 化妝 5 s
            Console.WriteLine("不管他,大姐姐化妝先"); Thread.Sleep(TimeSpan.FromSeconds(5));

            Console.WriteLine($"{DateTime.Now}:化完妝,等小弟弟打完游戲");
            thread.Join();

            Console.WriteLine("打完游戲了嘛?" + (!thread.IsAlive ? "true" : "false"));
            Console.WriteLine($"{DateTime.Now}:走,逛街去");
            Console.ReadKey();
        }

        public static void OneTest()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "開始打游戲");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"{DateTime.Now}:第幾局:" + i);
                Thread.Sleep(TimeSpan.FromSeconds(2));      // 休眠 2 秒
            }
            Console.WriteLine(Thread.CurrentThread.Name + "打完了");
        }

Join() 也可以實(shí)現(xiàn)簡單的線程同步,即一個(gè)線程等待另一個(gè)線程完成。

2.3 線程狀態(tài)

ThreadState 是一個(gè)枚舉,記錄了線程的狀態(tài),我們可以從中判斷線程的生命周期和健康情況。

其枚舉如下:

枚舉說明
Initialized0此狀態(tài)指示線程已初始化但尚未啟動(dòng)。
Ready1此狀態(tài)指示線程因無可用的處理器而等待使用處理器。 線程準(zhǔn)備在下一個(gè)可用的處理器上運(yùn)行。
Running2此狀態(tài)指示線程當(dāng)前正在使用處理器。
Standby3此狀態(tài)指示線程將要使用處理器。 一次只能有一個(gè)線程處于此狀態(tài)。
Terminated4此狀態(tài)指示線程已完成執(zhí)行并已退出。
Transition6此狀態(tài)指示線程在可以執(zhí)行前等待處理器之外的資源。 例如,它可能正在等待其執(zhí)行堆棧從磁盤中分頁。
Unknown7線程的狀態(tài)未知。
Wait5此狀態(tài)指示線程尚未準(zhǔn)備好使用處理器,因?yàn)樗诘却鈬僮魍瓿苫虻却Y源釋放。 當(dāng)線程就緒后,將對其進(jìn)行重排。

但是里面有很多枚舉類型是沒有用處的,我們可以使用一個(gè)這樣的方法來獲取更加有用的信息:

        public static ThreadState GetThreadState(ThreadState ts)
        {
            return ts & (ThreadState.Unstarted |
                ThreadState.WaitSleepJoin |
                ThreadState.Stopped);
        }

根據(jù) 2.2 中的示例,我們修改一下 Main 中的方法:

        static void Main(string[] args)
        {
            Thread thread = new Thread(OneTest);
            thread.Name = "小弟弟";

            Console.WriteLine($"{DateTime.Now}:大家在吃飯,吃完飯后要帶小弟弟逛街");
            Console.WriteLine("吃完飯了");
            Console.WriteLine($"{DateTime.Now}:小弟弟開始玩游戲");
            Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            thread.Start();
            Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            // 化妝 5 s
            Console.WriteLine("不管他,大姐姐化妝先"); Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            Console.WriteLine($"{DateTime.Now}:化完妝,等小弟弟打完游戲");
            thread.Join();
            Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            Console.WriteLine("打完游戲了嘛?" + (!thread.IsAlive ? "true" : "false"));
            Console.WriteLine($"{DateTime.Now}:走,逛街去");
            Console.ReadKey();
        }

代碼看著比較亂,請復(fù)制到項(xiàng)目中運(yùn)行一下。

輸出示例:

2020/4/11 11:01:48:大家在吃飯,吃完飯后要帶小弟弟逛街
吃完飯了
2020/4/11 11:01:48:小弟弟開始玩游戲
弟弟在干嘛?(線程狀態(tài)):Unstarted
弟弟在干嘛?(線程狀態(tài)):Running
不管他,大姐姐化妝先
小弟弟開始打游戲
2020/4/11 11:01:48:第幾局:0
2020/4/11 11:01:50:第幾局:1
2020/4/11 11:01:52:第幾局:2
弟弟在干嘛?(線程狀態(tài)):WaitSleepJoin
2020/4/11 11:01:53:化完妝,等小弟弟打完游戲
2020/4/11 11:01:54:第幾局:3
2020/4/11 11:01:56:第幾局:4
2020/4/11 11:01:58:第幾局:5
2020/4/11 11:02:00:第幾局:6
2020/4/11 11:02:02:第幾局:7
2020/4/11 11:02:04:第幾局:8
2020/4/11 11:02:06:第幾局:9
小弟弟打完了
弟弟在干嘛?(線程狀態(tài)):Stopped
打完游戲了嘛?true
2020/4/11 11:02:08:走,逛街去

可以看到 UnstartedWaitSleepJoin、Running、Stopped四種狀態(tài),即未開始(就緒)、阻塞、運(yùn)行中、死亡。

2.4 終止

.Abort() 方法不能在 .NET Core 上使用,不然會(huì)出現(xiàn) System.PlatformNotSupportedException:“Thread abort is not supported on this platform.” 。

后面關(guān)于異步的文章會(huì)講解如何實(shí)現(xiàn)終止。

由于 .NET Core 不支持,就不理會(huì)這兩個(gè)方法了。這里只列出 API,不做示例。

方法說明
Abort()在調(diào)用此方法的線程上引發(fā) ThreadAbortException,以開始終止此線程的過程。 調(diào)用此方法通常會(huì)終止線程。
Abort(Object)引發(fā)在其上調(diào)用的線程中的 ThreadAbortException以開始處理終止線程,同時(shí)提供有關(guān)線程終止的異常信息。 調(diào)用此方法通常會(huì)終止線程。

Abort() 方法給線程注入 ThreadAbortException 異常,導(dǎo)致程序被終止。但是不一定可以終止線程。

2.5 線程的不確定性

線程的不確定性是指幾個(gè)并行運(yùn)行的線程,不確定在下一刻 CPU 時(shí)間片會(huì)分配給誰(當(dāng)然,分配有優(yōu)先級(jí))。

對我們來說,多線程是同時(shí)運(yùn)行的,但一般 CPU 沒有那么多核,不可能在同一時(shí)刻執(zhí)行所有的線程。CPU 會(huì)決定某個(gè)時(shí)刻將時(shí)間片分配給多個(gè)線程中的一個(gè)線程,這就出現(xiàn)了 CPU 的時(shí)間片分配調(diào)度。

執(zhí)行下面的代碼示例,你可以看到,兩個(gè)線程打印的順序是不確定的,而且每次運(yùn)行結(jié)果都不同。

CPU 有一套公式確定下一次時(shí)間片分配給誰,但是比較復(fù)雜,需要學(xué)習(xí)計(jì)算機(jī)組成原理和操作系統(tǒng)。

留著下次寫文章再講。

        static void Main(string[] args)
        {
            Thread thread1 = new Thread(Test1);
            Thread thread2 = new Thread(Test2);

            thread1.Start();
            thread2.Start();

            Console.ReadKey();
        }

        public static void Test1()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Test1:" + i);
            }
        }
        public static void Test2()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Test2:" + i);
            }
        }

2.6 線程優(yōu)先級(jí)、前臺(tái)線程和后臺(tái)線程

Thread.Priority 屬性用于設(shè)置線程的優(yōu)先級(jí),Priority 是一個(gè) ThreadPriority 枚舉,其枚舉類型如下

枚舉說明
AboveNormal3可以將 安排在具有 Highest 優(yōu)先級(jí)的線程之后,在具有 Normal 優(yōu)先級(jí)的線程之前。
BelowNormal1可以將 Thread 安排在具有 Normal 優(yōu)先級(jí)的線程之后,在具有 Lowest 優(yōu)先級(jí)的線程之前。
Highest4可以將 Thread 安排在具有任何其他優(yōu)先級(jí)的線程之前。
Lowest0可以將 Thread 安排在具有任何其他優(yōu)先級(jí)的線程之后。
Normal2可以將 Thread 安排在具有 AboveNormal 優(yōu)先級(jí)的線程之后,在具有 BelowNormal 優(yōu)先級(jí)的線程之前。 默認(rèn)情況下,線程具有 Normal 優(yōu)先級(jí)。

優(yōu)先級(jí)排序:Highest > AboveNormal > Normal > BelowNormal > Lowest

Thread.IsBackgroundThread 可以設(shè)置線程是否為后臺(tái)線程。

前臺(tái)線程的優(yōu)先級(jí)大于后臺(tái)線程,并且程序需要等待所有前臺(tái)線程執(zhí)行完畢后才能關(guān)閉;而當(dāng)程序關(guān)閉是,無論后臺(tái)線程是否在執(zhí)行,都會(huì)強(qiáng)制退出。

2.7 自旋和休眠

當(dāng)線程處于進(jìn)入休眠狀態(tài)或解除休眠狀態(tài)時(shí),會(huì)發(fā)生上下文切換,這就帶來了昂貴的消耗。

而線程不斷運(yùn)行,就會(huì)消耗 CPU 時(shí)間,占用 CPU 資源。

對于過短的等待,應(yīng)該使用自旋(spin)方法,避免發(fā)生上下文切換;過長的等待應(yīng)該使線程休眠,避免占用大量 CPU 時(shí)間。

我們可以使用最為熟知的 Sleep() 方法休眠線程。有很多同步線程的類型,也使用了休眠手段等待線程(已經(jīng)寫好草稿啦)。

自旋的意思是,沒事找事做。

例如:

        public static void Test(int n)
        {
            int num = 0;
            for (int i=0;i<n;i++)
            {
                num += 1;
            }
        }

通過做一些簡單的運(yùn)算,來消耗時(shí)間,從而達(dá)到等待的目的。

C# 中有關(guān)于自旋的自旋鎖和 Thread.SpinWait(); 方法,在后面的線程同步分類中會(huì)說到自旋鎖。

Thread.SpinWait() 在極少數(shù)情況下,避免線程使用上下文切換很有用。其定義如下

public static void SpinWait(int iterations);

SpinWait 實(shí)質(zhì)上是(處理器)使用了非常緊密的循環(huán),并使用 iterations 參數(shù)指定的循環(huán)計(jì)數(shù)。 SpinWait 等待時(shí)間取決于處理器的速度。

SpinWait 無法使你準(zhǔn)確控制等待時(shí)間,主要是使用一些鎖時(shí)用到,例如 Monitor.Enter。

“C#線程的創(chuàng)建和生命周期實(shí)例分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI