C#線程學習筆記一:線程基礎

来源:https://www.cnblogs.com/atomy/archive/2019/11/28/11946506.html
-Advertisement-
Play Games

本筆記摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/Thread.html,記錄一下學習過程以備後續查用。 一、線程的介紹 進程(Process)是應用程式的實例要使用的資源的一個集合,每個應用程式都在各自的進程中運行來確保應用程式不受其他 ...


    本筆記摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/Thread.html,記錄一下學習過程以備後續查用。

    一、線程的介紹

    進程(Process)是應用程式的實例要使用的資源的一個集合,每個應用程式都在各自的進程中運行來確保應用程式不受其他應用程式的影響。

    線程是進程中基本執行單元, 一個進程中可以包含多個線程。在進程入口執行的第一個線程是一個進程的主線程,在.NET應用程式中,都是以Main()方法

作為程式的入口(線程是進程的執行單元,進程是線程的一個容器)。

    二、線程調度和優先順序  

    Windows之所以被稱為搶占式多線程操作系統,是因為線程可以在任意時間被搶占,並調度另一個線程。

    每個線程都分配了從0~31的一個優先順序,系統首先把高優先順序的線程分配給CPU執行。

    Windows 支持7個相對線程優先順序:Idle、Lowest、Below Normal、Normal、Above Normal、Highest和Time-Critical。Normal是預設的線程優先順序,

然而在程式中可以通過設置Thread的Priority屬性來改變線程的優先順序,它的類型為ThreadPriority枚舉類型:Lowest、BelowNormal、Normal、AboveNormal

和Highest,CLR為自己保留了 Idle和Time-Critical優先順序。

    枚舉值列表如下:

成員名稱 說明
Lowest 可以將Thread置於其他優先順序線程之後。
BelowNormal 可以將Thread置於Normal優先順序線程之後Lowest優先順序線程之前。
Normal

可以將Thread置於AboveNormal優先順序線程之後BelowNormal優先順序線程之前。

預設情況下,線程置於Normal優先順序。

AboveNormal 可以將Thread置於Highest優先順序線程之後Normal優先順序線程之前。
Highest 可以將Thread置於其他優先順序線程之前。

    三、前臺線程和後臺線程

    在.NET中線程分為前臺線程和後臺線程:

    1、主線程是程式開始時就執行的,如果你需要再創建線程,那麼創建的線程就是這個主線程的子線程,它是前臺線程。 

    2、子線程可以是前臺線程也可以是後臺線程。

    3、前臺線程必須全部執行完,即使主線程關閉掉,這時進程仍然存活。

    4、當所有前臺線程停止運行時,CLR會強制結束仍在運行的任何後臺線程,這些後臺線程直接被終止,不會拋出異常。

    5、前臺線程與後臺線程唯一的區別是後臺線程不會阻止進程終止,可以在任何時候將前臺線程修改為後臺線程。

        static void Main(string[] args)
        {
            ThreadType();
        }

        /// <summary>
        /// 前臺線程與後臺線程
        /// </summary>
        private static void ThreadType()
        {
            //創建一個新線程(預設為前臺線程)
            Thread backThread = new Thread(Worker)
            {
                //將線程更改為後臺線程
                IsBackground = true
            };

            //通過Start方法啟動線程
            backThread.Start();

            //如果backThread是前臺線程,則應用程式5秒後才終止。
            //如果backThread是後臺線程,則應用程式立即終止。
            Console.WriteLine("It's from main thread.");
            //Console.Read();
        }

        private static void Worker()
        {
            //休息5秒
            Thread.Sleep(5000);
            Console.WriteLine("It's from worker thread.");
        }

    假如保留IsBackground = true;但又想繼續執行Worker()方法的話,可以調用Thread.Join()方法來實現。Join()方法能保證主線程(前臺線程)在非同步線程

Thread(後臺線程)運行結束後才會運行。

    註1:一個線程在執行的過程中,可能調用另一個線程,前者可以稱為調用線程,後者成為被調用線程。

    註2:Thread.Join()方法的使用場景:調用線程掛起,等待被調用線程執行完畢後,繼續執行。

    註3:被調用線程執行Join方法,告訴調用線程,你先暫停,我執行完了,你再執行。從而保證了先後關係。

        static void Main(string[] args)
        {
            ThreadStatusChange();
        }

        /// <summary>
        /// 線程狀態之間的轉換
        /// </summary>
        private static void ThreadStatusChange()
        {
            //創建一個新線程(預設為前臺線程)
            Thread backThread = new Thread(Worker)
            {
                //將線程更改為後臺線程
                IsBackground = true
            };

            //通過Start方法啟動線程
            backThread.Start();

            //Join()方法能保證主線程(前臺線程)在非同步線程Thread(後臺線程)運行結束後才會運行
            backThread.Join();

            Console.WriteLine("It's from main thread.");
            Console.Read();
        }

        private static void Worker()
        {
            //休息5秒
            Thread.Sleep(5000);
            Console.WriteLine("It's from worker thread.");
        }

    運行結果如下:

    四、 Suspend和Resume方法

    這兩個方法在.NET Framework 1.0的時候就支持的方法,他們分別可以掛起線程及恢復掛起的線程,但在.NET Framework 2.0以後的版本中這兩個方法都過時了。

    MSDN的解釋是這樣:

    警告:

    不要使用Suspend和Resume方法來同步線程的活動。您無法知道掛起線程時它正在執行什麼代碼。如果您在安全許可權評估期間掛起持有鎖的線程,

則AppDomain中的其他線程可能被阻止。如果您線上程正在執行類構造函數時掛起它,則 AppDomain中嘗試使用該類的其他線程將被阻止。這樣很容易發生死鎖。

        static void Main(string[] args)
        {
            ThreadResume();
        }

        /// <summary>
        /// 線程恢復
        /// </summary>
        private static void ThreadResume()
        {
            Thread thread = new Thread(ThreadSuspend)
            {
                Name = "Thread1"
            };
            thread.Start();
            Thread.Sleep(2000);
            Console.WriteLine("Main Thread is running.");
            //線程恢復
            thread.Resume();
            Console.Read();
        }

        /// <summary>
        /// 線程掛起
        /// </summary>
        private static void ThreadSuspend()
        {
            Console.WriteLine("Thread: {0} has been suspended.", Thread.CurrentThread.Name);
            //將當前線程掛起
            Thread.CurrentThread.Suspend();
            Console.WriteLine("Thread: {0} has been resumed.", Thread.CurrentThread.Name);
        }

    在上面這段代碼中Thread1線程是在主線程中恢復的,但當主線程發生異常時,這時候Thread1就會一直處於掛起狀態,此時Thread1所使用的資源就不能釋放

(除非強制終止進程),當其它的線程需要使用這快資源的時候, 很有可能就會發生死鎖現象。

    上面一段代碼還存在一個隱患,假如把Thread.Sleep(2000);這段代碼註釋一下:

        static void Main(string[] args)
        {
            ThreadResume();
        }

        /// <summary>
        /// 線程恢復
        /// </summary>
        private static void ThreadResume()
        {
            Thread thread = new Thread(ThreadSuspend)
            {
                Name = "Thread1"
            };
            thread.Start();
            //Thread.Sleep(2000);
            Console.WriteLine("Main Thread is running.");
            //線程恢復
            thread.Resume();
            Console.Read();
        }

        /// <summary>
        /// 線程掛起
        /// </summary>
        private static void ThreadSuspend()
        {
            Console.WriteLine("Thread: {0} has been suspended.", Thread.CurrentThread.Name);
            //將當前線程掛起
            Thread.CurrentThread.Suspend();
            Console.WriteLine("Thread: {0} has been resumed.", Thread.CurrentThread.Name);
        }

    這個時候,主線程因為跑(運行)得太快,做完自己的事情去喚醒Thread1時,此時Thread1還沒有掛起,而此時喚醒Thread1就會出現異常了。

    五、Abort和Interrupt方法

    Abort方法和Interrupt都是用來終止線程的,但是兩者還是有區別的:

    1、它們拋出的異常不一樣:Abort 方法拋出的異常是ThreadAbortException,Interrupt拋出的異常為ThreadInterruptedException。

    2、調用Interrupt方法的線程之後可以被喚醒,然而調用Abort方法的線程就直接被終止不能被喚醒了。

    下麵演示Abort方法的使用:

        static void Main(string[] args)
        {
            //ThreadType();
            //ThreadStatusChange();
            //ThreadResume();
            ThreadAbort();
        }        
        
        /// <summary>
        /// 線程中斷(不可再喚醒)
        /// </summary>
        private static void ThreadAbort()
        {
            Thread threadAbort = new Thread(AbortMethod)
            {
                Name = "ThreadAbort"
            };
            threadAbort.Start();
            Thread.Sleep(1000);
            try
            {
                threadAbort.Abort();
            }
            catch
            {
                Console.WriteLine("1-> {0} exception happen in main thread.", Thread.CurrentThread.Name);
                Console.WriteLine("2-> {0} status is:{1} in main thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
            }
            finally
            {
                Console.WriteLine("3-> {0} status is:{1} in main thread.", threadAbort.Name, threadAbort.ThreadState);
            }
            threadAbort.Join();
            Console.WriteLine("4-> {0} status is:{1}", threadAbort.Name, threadAbort.ThreadState);
            Console.Read();
        }

        /// <summary>
        /// Abort方法
        /// </summary>
        private static void AbortMethod()
        {
            try
            {
                Thread.Sleep(5000);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.GetType().Name);
                Console.WriteLine("5-> {0} exception happen in abort thread.", Thread.CurrentThread.Name);
                Console.WriteLine("6-> {0} status is:{1} in abort thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
            }
            finally
            {
                Console.WriteLine("7-> {0} status is:{1} in abort thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
            }
        }

    運行結果如下:

    從運行結果可以看出,調用Abort方法的線程引發的異常類型為ThreadAbortException,另外異常只會在調用Abort方法的線程中發生,而不會在主線程中拋出,

其次調用Abort方法後線程的狀態不是立即改變為Aborted狀態,而是從AbortRequested->Aborted。 

    下麵演示Interrupt方法的使用:

        static void Main(string[] args)
        {
            ThreadInterrupt();
        }
        
        /// <summary>
        /// 線程中斷(可再喚醒)
        /// </summary>
        static void ThreadInterrupt()
        {
            Thread threadInterrupt = new Thread(InterruptMethod)
            {
                Name = "ThreadInterrupt"
            };
            threadInterrupt.Start();
            threadInterrupt.Interrupt();
            threadInterrupt.Join();
            Console.WriteLine("1-> {0} status is:{1} ", threadInterrupt.Name, threadInterrupt.ThreadState);
            Console.Read();
        }

     
        /// <summary>
        /// Interrupt方法
        /// </summary>
        private static void InterruptMethod()
        {
            try
            {
                Thread.Sleep(5000);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.GetType().Name);
                Console.WriteLine("2-> {0} exception happen in interrupt thread.", Thread.CurrentThread.Name);
                Console.WriteLine("3-> {0} status is:{1} in interrupt thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
            }
            finally
            {
                Console.WriteLine("4-> {0} status is:{1} in interrupt thread.", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
            }
        }

    運行結果如下:

    從結果中可以得到,調用Interrupt方法拋出的異常為:ThreadInterruptException, 另外當調用Interrupt方法後線程的狀態應該是中斷的,但是從運行結果看,

此時的線程因為Join、Sleep方法而喚醒了線程。

    為了進一步解釋調用Interrupt方法的線程可以被喚醒, 我們可以線上程執行的方法中運用迴圈,如果線程可以喚醒,則輸出結果中就一定會有迴圈的部分,

而調用Abort方法的線程則不會有迴圈的部分。

    下麵代碼相信大家看後肯定會更加理解兩個方法的區別:

        static void Main(string[] args)
        {
            //ThreadType();
            //ThreadStatusChange();
            //ThreadResume();
            //ThreadAbort();
            //ThreadInterrupt();
            ThreadWake();
        }

        /// <summary>
        /// 線程喚醒
        /// </summary>
        static void ThreadWake()
        {
            Thread threadWake = new Thread(WakeMethod);
            threadWake.Start();
            Thread.Sleep(100);

            threadWake.Interrupt();
            Thread.Sleep(3000);
            Console.WriteLine("1-> After finally block,the threadWake status is:{0}", threadWake.ThreadState);
            Console.Read();
        }

        /// <summary>
        /// Wake方法
        /// </summary>
        private static void WakeMethod()
        {
            for (int i = 0; i < 4; i++)
            {
                try
                {
                    Thread.Sleep(2000);
                    Console.WriteLine("2-> Thread is Running.");
                }
                catch (Exception ex)
                {
                    if (ex != null)
                    {
                        Console.WriteLine("3-> Exception {0} throw.", ex.GetType().Name);
                    }
                }
                finally
                {
                    Console.WriteLine("4-> Current thread status is:{0}", Thread.CurrentThread.ThreadState);
                }
            }
        }

    運行結果如下:

    如果把上面的threadWake.Interrupt();改為threadWake.Abort(); 運行結果為:

    六、簡單線程的使用

    其實在上面介紹前臺線程和後臺線程的時候已經通過ThreadStart委托創建一個線程了,此時已經實現了一個多線程的一個過程。

    下麵通過ParameterizedThreadStart委托的方式來實現多線程:

        static void Main(string[] args)
        {
            ThreadTypeUseParameterized();
        }

        /// <summary>
        /// 前臺線程與後臺線程(使用ParameterizedThreadStart委托的方式來實現多線程)
        /// </summary>
        private static void ThreadTypeUseParameterized()
        {
            //創建一個新線程(預設為前臺線程)
            Thread backThread = new Thread(new ParameterizedThreadStart(Worker1));

            //通過Start方法啟動線程
            backThread.Start(123);

            //如果backThread是前臺線程,則應用程式5秒後才終止。
            //如果backThread是後臺線程,則應用程式立即終止。
            Console.WriteLine("It's from main thread.");
        }

        private static void Worker1(object parameter)
        {
            //休息5秒
            Thread.Sleep(5000);
            Console.WriteLine(parameter+" is from worker1 thread.");
            Console.Read();
        }

    運行結果如下:

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • go gui 控制項和信號 控制項 控制項簡介 控制項是對數據和方法的封裝。控制項有自己的屬性和方法。屬性是指控制項的特征。方法是指控制項的一些簡單而可見的功能。如按鈕就是一個控制項,這個按鈕是方形的,裡面有張圖片,這是我們能看到外觀屬性,同時,這個按鈕具備被人按下的功能。 GTK中控制項主要分為兩類:容器控制項,非容 ...
  • struct interface 就可以實現面向對象中的繼承,封裝,多態 繼承的演示:Tsh類型繼承People類型,並且使用People類型的方法 多態的演示Tsh類型實現了介面Student,實現了介面定義的方法 完整代碼: ...
  • ```python import pymysql conn=pymysql.connect(host='localhost',user='root',password='123',db='sg',charset='utf8') #先修路-conn car = conn.cursor() #備車-ca... ...
  • 場景 使用ElementUI的快速開始的項目模板搭建Element項目後, 要在vue頁面中使用jquery的語法。 這裡直接使用$.ajax會提示$找不到。 註: 博客:https://blog.csdn.net/badao_liumang_qizhi關註公眾號霸道的程式猿獲取編程相關電子書、教程 ...
  • 封裝一個資料庫模塊有三個功能:查詢,插入,關閉 1.查看 2.提交 3.關閉 ...
  • 前言 作為一個小白,在學習之前,我非常的明確,自己要學什麼編程語言。 怎麼判斷某門編程語言掉不掉發? 說起掉發,在前言中講過,程式員很多掉發原因都是因為選“錯”了編程語言,接下來讓我們看看編程語言各個撞死人(創始人)的發量是有多麼的恐怖! PHP之父:拉斯馬斯·勒德爾夫 拉斯馬斯·勒德爾夫,創建PH ...
  • 數據結構 數據結構(演算法)的介紹 數據結構的介紹 1) 數據結構是一門研究演算法的學科,只從有了編程語言也就有了數據結構.學好數據結構可以編寫 出更加漂亮,更加有效率的代碼。 2) 要學習好數據結構就要多多考慮如何將生活中遇到的問題,用程式去實現解決. 3) 程式 = 數據結構 + 演算法 20.2 數 ...
  • 一、前言 在概念上, click 把命令行分為 3 個組成:參數、選項和命令。 參數 就是跟在命令後的除選項外的內容,比如 git add a.txt 中的 a.txt 就是表示文件路徑的參數 選項 就是以 或 開頭的參數,比如 f、 file 命令 就是命令行的初衷了,比如 git 就是命令,而 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...