C# 執行緒與任務

唯有自己強大發表於2022-03-18

執行緒

執行緒:對於所有需要等待的操作,例如移動檔案,資料庫和網路訪問都需要一定的時間,此時就可以啟動一個新的執行緒,同時完成其他任務。一個程式的多個執行緒可以同時執行在不同的CPU上或多核CPU的不同核心上。

一個應用程式啟動時,會啟動一個程式(應用程式的載體),然後程式會啟動多個執行緒。

一,使用Thread類啟動執行緒和資料傳輸

使用Thread類可以建立和控制執行緒,Thread建構函式是一個無參無返回值的委託型別。

1️⃣對Thread傳入一個無參無返回型別的方法-ThreadStart。

    public delegate void ThreadStart();

例項:

        static  void test()
        {
            Console.WriteLine("test is start");
            Console.WriteLine("test is running");
            Thread.Sleep(3000);
            Console.WriteLine("test is completed");
        }

        static void Main(string[] args)
        {
            Thread th = new Thread(test);
            th.Start();
            Console.WriteLine("main is completed");
        }

2️⃣對Thread傳入一個匿名方法(或lambda表示式)。用於傳入的方法程式碼簡單的情況

        static void Main(string[] args)
        {
            //lambad表示式
            Thread th = new Thread(()=> {
                Console.WriteLine("子執行緒1-ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            th.Start();
            //匿名方法
            Thread th2 = new Thread(delegate ()
            {
                Console.WriteLine("子執行緒2-ID:" + Thread.CurrentThread.ManagedThreadId);
            });
            th2.Start();
            Console.WriteLine("main is completed");
        }

 

 3️⃣對Thread傳入一個無返回值有參方法-ParameterizedThreadStart,該引數只能是object型別且只能有一個引數。

    public delegate void ParameterizedThreadStart(object? obj);

 例項:

        static void download(object o)
        {
            string str = o as string;
            Console.WriteLine("地址:" + str);
        }
        static void Main(string[] args)
        {
            Thread th = new Thread(download);
            th.Start("http://baidu.com");
            Console.WriteLine("main is completed");
        }

 注意:使用as進行強制型別轉換 成功會正確輸出,失敗會輸出null。

以上資料傳輸的方法都是基於靜態變數進行傳輸的,但是定義過多靜態變數會導致多個執行緒訪問同一個靜態變數,造成資源衝突。

靜態變數雖然方便訪問,但是靜態的一般都是公共的,容易混亂。

4️⃣對Thread傳入一個無返回值多個引數的方法(定義一個結構體),但是不能用as強制轉換。

        public struct data
        {
            public string message;
            public int age;
        }
        static void download(object o)
        {
            data str = (data)o;//強制型別轉換
            Console.WriteLine("資訊:" + str.message);
            Console.WriteLine("年紀:" + str.age);
        }
        static void Main(string[] args)
        {
            data da = new data();
            da.message = "sss";
            da.age = 3;
            Thread th = new Thread(download);
            th.Start(da);
            Console.WriteLine("main is completed");
        }

由於結構體是值型別,不能為null,因此不能用as進行強制型別轉換。

 5️⃣通過自定義類傳遞資料(即將通過執行緒呼叫一個類的成員方法)

先建立一個download類:

    class downLoad
    {
        public string URL { get; private set; }
        public string Message { get; private set; }
        //建構函式
        public downLoad(string uRL, string message)
        {
            URL = uRL;
            Message = message;
        }
        //下載方法
        public void  load()
        {
            Console.WriteLine("" + URL + "獲取資訊:" + Message);
        }
    }

在主程式中將該類的成員方法傳入Thread中:

   static void Main(string[] args)
        {
            var download = new downLoad("www.baidu.com", "mp4");
            Thread th = new Thread(download.load);
            th.Start();
            Console.WriteLine("main is completed");
            Console.ReadKey();
        }

?知識點擴充1-前臺執行緒與後臺執行緒:

應用程式的程式需要等待所有前臺執行緒完成其任務後才會結束。而後臺執行緒在應用程式關閉後會自動關閉,即使後臺執行緒還沒有執行完畢。在預設情況下,用Thread類建立的執行緒是前臺執行緒,執行緒池中的執行緒是後臺執行緒。在Thread類建立執行緒的時候,可以設定IsBackground屬性,表示它是否是一個後臺執行緒。

?知識點擴充2-執行緒的優先順序

執行緒有作業系統排程,一個CPU同一時間只能做一件事(執行一個執行緒中的計算任務),當有很多執行緒需要CPU執行時,執行緒排程器會根據執行緒的優先順序去判斷先去執行哪個執行緒,如果優先順序相同,就使用一個迴圈排程規則,逐個執行每個執行緒。

在Thread類中,可以設定Priority屬性,以影響執行緒的基本優先順序,Priority屬性一個ThreadPriority列舉定義的一個值,定義級別有Highest,AboveNormal,Normal,BelowNormal,和Lowest。

因此對於重要的執行緒任務,可以將執行緒優先順序設定高一點,使其可以儘快執行完畢。

如果需要等待執行緒執行結果在執行後面的程式碼,可以呼叫Thread物件的join方法,即將該執行緒加入進來,並停止當前執行緒,直至加入的執行緒執行完畢。

二,執行緒池ThreadPool類

由於執行緒的建立需要時間,如果有不同的小任務要完成,就可以事先建立多個執行緒。系統有一個ThreadPool類來管理執行緒,這個類會在需要執行緒的時候增加執行緒數,不需要時候減少。池中最大執行緒數是可配置的。在雙核CPU中,預設設定為1023個工作執行緒和1000個IO執行緒,如果需要更多執行緒(超過了執行緒池的最大數量),最新的任務就需要排隊等待。

使用執行緒池,即呼叫ThreadPool.QueueUserWorkItem方法,該方法需要傳入一個WaitCallBack型別的委託(即傳入帶一個object引數的方法)。然後ThreadPool會在池中找一個空閒的執行緒去執行傳入的方法。

        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(work);              
            }
            Thread.Sleep(10000);
        }
        static void work(object state)
        {
            Console.WriteLine("執行緒id" + Thread.CurrentThread.ManagedThreadId);
        }

 需要注意的是:

  • 執行緒池中所有執行緒都是後臺執行緒,如果程式中所有的前臺執行緒都結束了,所有的後臺執行緒也會跟著結束。不能把入池的後臺執行緒改為前臺執行緒。
  • 不能給入池的執行緒設定優先順序或名稱。
  • 入池的執行緒只能是用於時間較短的任務。如果執行緒要一直執行,就應用Thread類建立一個執行緒。

 任務

任務表示應完成某個單元的工作,這個工作可以在單獨的執行緒中執行,也可以同步方式啟動一個任務。任務在後臺使用ThreadPool進行管理的,也就是說任務啟動的也是後臺執行緒。

一,建立並啟動任務

 啟動任務的兩種方式:

        static void Main(string[] args)
        {
            //第一種:使用TaskFactory
            TaskFactory tf = new TaskFactory();
            tf.StartNew(work);
            //第二種:使用Task
            Task t = new Task(work);
            t.Start();
            Console.WriteLine("main is completed");
        }
        static void work()
        {
            Console.WriteLine("執行緒id" + Thread.CurrentThread.ManagedThreadId);
        }

 需要注意的是:使用TaskFactory建立任務,傳入的方法為無參的。

二,連續任務

 如果一個任務t1的執行是依賴於另一個任務t2,那麼就需要在t2執行完畢後才開始執行t1。(例如:迅雷下載完成後彈出介面提示)這時候我們可以使用連續任務ContinueWith

        static void Main(string[] args)
        {
            Task t1 = new Task(download);
            Task t2 = t1.ContinueWith(show);
            t1.Start();
            Thread.Sleep(2000);
        }
        static void download()
        {
            Console.WriteLine("正在下載中。。。。");
            Thread.Sleep(1000);
        }
        static void show(Task t)
        {
            Console.WriteLine("下載完成!");
        }

  需要注意的是:傳入t2的方法的引數需為Task型別。

 三,資源衝突問題

 在多執行緒中如果多個執行緒同時訪問同一資源,就會產生資源衝突的問題。這時候需要用lock對程式進行加鎖。

?什麼是資源衝突?

    class state
    {
        public int num = 5;
        public void checknum()
        {
            if (num==5)
            {
                num++;
                Console.WriteLine("num的值:" + num + "當前執行緒id:" + Thread.CurrentThread.ManagedThreadId); 
            }
            num =5;
        }
    }
  static void Main(string[] args)
        {
            state st = new state();
            for (int i = 0; i < 10; i++)
            {
                Thread th = new Thread(st.checknum);
                th.Start();
            }
        }

在主程式使用多執行緒呼叫state類的check方法,可以看到num=5和num=6造成資源衝突了。

 ?對多個執行緒訪問的物件進行加鎖

        object _lock = new object();
        public int num = 5;
        public void checknum()
        {
            lock(_lock)
            {
            if (num==5)
            {
                num++;
                Console.WriteLine("num的值:" + num + "當前執行緒id:" + Thread.CurrentThread.ManagedThreadId); 
            }
            num =5;
            }
        }

 

相關文章