1. 程式人生 > >多執行緒之旅(Thread)

多執行緒之旅(Thread)

      在上篇文章中我們已經知道了多執行緒是什麼了,那麼它到底可以幹嘛呢?這裡特別宣告一個前面的委託沒看的同學可以到上上上篇博文檢視,因為多執行緒要經常使用到委託。原始碼

一、非同步、同步

      1.同步(在計算的理解總是要你措不及防,同步當執行緒做完一件事情之後,才會執行後續動作),同步方法慢,只有一個執行緒執行,非同步方法快,因為多個執行緒一起幹活,但是兩者並不是線性增長,當我們的非同步執行緒佔有的資源越來越多了,會導致資源可能不夠,其次執行緒過多CPU也是需要管理成本的,所以不是越多越好。

      2.非同步(可以同時執行多個任務,在同樣的時間,執行不同的任務),同步方法卡介面(UI),因為我們的主執行緒(UI)忙於計算造成了堵塞了。非同步方法不卡介面,計算任務交給了子執行緒完成。winform中體現的玲玲精緻。(你品,你細品),web 可以非同步的處理一起其他的任務,比如給使用者發郵箱(我們的BS結構的,每次訪問都是一個子執行緒,當我們的程式碼寫的比較糟糕,是不是載入比較慢呢哈哈)。非同步多執行緒無序,執行的先後無序,執行的時間不確定,結束也不確定,所以我們很難通過執行時間和先後順序控制,非同步的執行順序。

二、初識Thread

屬性名稱說明
CurrentContext 獲取執行緒正在其中執行的當前上下文。
CurrentThread 獲取當前正在執行的執行緒。
ExecutionContext 獲取一個 ExecutionContext 物件,該物件包含有關當前執行緒的各種上下文的資訊。
IsAlive 獲取一個值,該值指示當前執行緒的執行狀態。
IsBackground 獲取或設定一個值,該值指示某個執行緒是否為後臺執行緒。
IsThreadPoolThread 獲取一個值,該值指示執行緒是否屬於託管執行緒池。
ManagedThreadId 獲取當前託管執行緒的唯一識別符號。
Name 獲取或設定執行緒的名稱。
Priority 獲取或設定一個值,該值指示執行緒的排程優先順序。
ThreadState 獲取一個值,該值包含當前執行緒的狀態。

Thread 中包括了多個方法來控制執行緒的建立、掛起、停止、銷燬,後面的例子中會經常使用。

方法名稱說明
Abort()     終止本執行緒。
GetDomain() 返回當前執行緒正在其中執行的當前域。
GetDomainId() 返回當前執行緒正在其中執行的當前域Id。
Interrupt() 中斷處於 WaitSleepJoin 執行緒狀態的執行緒。
Join() 已過載。 阻塞呼叫執行緒,直到某個執行緒終止時為止。
Resume() 繼續執行已掛起的執行緒。
Start()   執行本執行緒。
Suspend() 掛起當前執行緒,如果當前執行緒已屬於掛起狀態則此不起作用
Sleep()   把正在執行的執行緒掛起一段時間。

      1.Thread是我們.NET 1.0 給我們提供的多執行緒類,可以建立,和控制多執行緒,Thread類建構函式為接受ThreadStart和ParameterizedThreadStart型別的委託引數,下面有請程式碼神君。

        /// <summary>
        /// 使用Thread 建立多執行緒
        /// </summary>
        public static void Show()
        {
            //例項化建立執行緒 無參無返回值
            Thread thread = new Thread(() =>
            {
                Console.WriteLine("我是多執行緒");
            });
            thread.Start();

            //建立5個執行緒1
            for (int i = 0; i < 5; i++)
            {
                //這個之所以建立一個k,後面執行緒不安全會說到
                var k = i;
                //這是一個有引數無返回值多執行緒
                new Thread(x => Running(Convert.ToInt32(x))).Start(k);
            }
            Console.Read();
        }

 /// <summary>
        /// 一個執行需要長時間的任務
        /// </summary>
        static void Running(int s)
        {
            Console.WriteLine("**********************************");
            Console.WriteLine("執行開始啦" + s);
            Console.WriteLine("獲取當前執行的執行緒ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
            var j = 0;
            for (int i = 0; i < 1000000000; i++)
            {
                j++;
            }
            Console.WriteLine("執行結束啦" + s);
        }
View Code

二、漸入佳境

  1.執行上面的程式碼,可以看到執行緒的無序性,雖然我們的0最先開始執行的,但是不是第一個結束的,這個是因為我們每個執行緒執行的時間的不確定性。這裡也要特別說明為什麼Thread建構函式傳遞的是ThreadStart和ParameterizedThreadStart型別的委託引數,為什麼不是Action ,Func,答案就是.NET 1.0的時候還沒有Action 、Func。ThreadStart委託是一個無參無返回值上程式碼中我們建立了,ParameterizedThreadStart委託是一個有引數無返回值,但是我們可以看到我們的引數是一個object型別,是一個不安全的引數(當時泛型也沒有出來)當然為了防止這問題,我們也是想到了方法,那就是我們可以通過一個泛型類,幫我們限制引數型別。

        /// <summary>
        /// 防止引數不安全
        /// </summary>
        public static void Show5()
        {
            //我們建立一個泛型類,限制我們的型別
            MyThread<string> mythread = new MyThread<string>("Thread_child");
            //將我們的方法傳遞,進去
            Thread th3 = new Thread(mythread.ThreadChild);
            //啟動執行緒
            th3.Start();
        }

        /// <summary>
        /// 建立一個泛型類
        /// </summary>
        /// <typeparam name="T"></typeparam>
        class MyThread<T>
        {
            private T data;
            public MyThread(T data)
            {
                this.data = data;
            }
            public void ThreadChild()
            {
                Console.WriteLine("Child Thread Start! Result:{0}", data);
            }
        }
View Code

  2.我們在上面還提供了其他的方法,但是這些方法已經不建議使用了,現在已經棄用了,因為我們無法精確地控制執行緒的開啟與暫停,當我們將執行緒掛起的時候,同時也會掛起執行緒使用的資源,會導致死鎖,不建議使用。將執行緒銷燬也不建議    不一定及時/有些動作發出收不回來。(這裡我使用的是.net Core 3.1 執行直接報錯了哈哈)

        /// <summary>
        /// 使用Thread 執行緒掛起、喚醒執行緒、銷燬,方式是拋異常、取消Abort異常
        /// </summary>
        public static void Show1()
        {
            //建立一個Thread 執行緒
            Thread thread = new Thread(() =>
            {
                Running();
            });
            //開啟執行緒
            thread.Start();
            //這個是執行緒掛起
            //thread.Suspend();
            //喚醒執行緒
            //thread.Resume();
            //上面的兩個方法,現在已經棄用了,因為我們無法精確地控制執行緒的開啟與暫停
            //當我們將執行緒掛起的時候,同時也會掛起執行緒使用的資源,會導致死鎖,不建議使用
            try
            {
                //將執行緒銷燬
                //也不建議    不一定及時/有些動作發出收不回來
                thread.Abort();
            }
            catch (Exception)
            {
                //靜態方法將執行緒異常取消繼續工作
                Thread.ResetAbort();
            }
            Console.Read();
        }
View Code

   3.執行緒優先順序,當然我們的執行緒是一個無序的,也有控制執行緒執行的權重,但是這個優先順序不是絕對的,因為執行緒的執行順序還是看我們的CPU爸爸的,但是我們可以利用Priority屬性做執行緒的權重執行,使用也很簡單

  /// <summary>
        /// 使用Thread 執行緒的優先順序(但是執行還是看CPU,可以做優先順序,但是不是絕對優先)
        /// </summary>
        public static void Show3()
        {
            //建立一個Thread 執行緒
            Thread thread = new Thread(() =>
            {
                Running();
            });
            thread.Start();
            //thread.Priority屬性可以設定執行緒的優先順序關係
            thread.Priority = ThreadPriority.Highest;
            Console.WriteLine("執行完啦啦啦啦啦啦啦啦啦啦啦拉拉");
            Console.Read();
        }
View Code

  4.前臺執行緒、後臺執行緒(這個字面意思,還是和我們的理解是不一樣的)我們設定IsBackground控制執行緒是否(前/後)臺執行緒。預設是前臺執行緒,啟動之後一定要完成任務的,阻止程序退出。指定後臺執行緒:隨著程序退出。

 三、多執行緒起飛

  1、非同步回撥

    1.我們的Thread沒有給我提供非同步回撥的功能,沒辦法需要自己造輪子了,我們可以先想一下回調的需求是什麼,需求分析:當我們的執行緒任務執行完之後需要之後某些方法。我們細品一下,我們要執行完之後,在執行一個人任務,那就是同步執行非同步方法了吧。我們在子執行緒中怎麼同步執行呢?下面的程式碼就實現了回撥功能不管我們執行多少次回撥總會在任務後面執行。

/// <summary>
        /// 非同步回撥執行
        /// </summary>
        public static void Show6() {
            //建立一個任務委託
            ThreadStart threadStart = () => {
                Console.WriteLine("我是任務");
            };
            //建立一個回撥執行的委託
            Action action = () => {
                Console.WriteLine("哈哈,我就是你們的回撥方法哈,記得雙擊麼麼噠");
                Console.WriteLine("*********************************************");
            };
            ThreadWithCallback(threadStart, action);
            Console.ReadLine();
        }

/// <summary>
        /// 回撥封裝 無返回值
        /// </summary>
        /// <param name="start"></param>
        /// <param name="callback">回撥</param>
        private static void ThreadWithCallback(ThreadStart start, Action callback)
        {
            Thread thread = new Thread(() =>
            {
                start.Invoke();
                callback.Invoke();
            });
            thread.Start();
        }
View Code

   2、返回引數

    1.當然我們使用執行緒需要返回引數,但是我們的Thread沒有給我們提供返回值的委託和方法,這個要莫子搞羅?當然我們先分析需求,我們要獲取返回值是不是要等執行緒執行之後呢?好的執行緒執行我們可以使用Join堵塞執行緒等它執行完畢,但是我們要怎麼獲取返回值呢?對了我們可以建立一個變數,我們的執行緒給變數賦值嗎?

/// <summary>
        /// 非同步返回值
        /// </summary>
        public static void Show7()
        {
            //建立一個委託
            Func<string> func = () => {
                return "我是返回值";
            };
            //獲取執行結果
            Console.WriteLine(ThreadWithReturn(func).Invoke());
            Console.ReadLine();
        }

/// <summary>
        /// 有返回值封裝(請根據本案例自行封裝回調)
        /// </summary>
        /// <typeparam name="T">返回值型別</typeparam>
        /// <param name="func">需要子執行緒執行的方法</param>
        /// <returns></returns>
        private static Func<T> ThreadWithReturn<T>(Func<T> func)
        {
            //初始化一個泛型,限制我們的型別
            T t = default(T);
            ThreadStart newStart = () =>
            {
                //執行緒給變數賦值
                t = func.Invoke();
            };
            //建立執行緒
            Thread thread = new Thread(newStart);
            //執行執行緒
            thread.Start();
            //建立一個委託 無參有返回值,執行委託會發生執行執行緒等待堵塞
            //當執行緒執行完之後,也就是說執行緒已經給變數t賦值了,我們就返回t
            return new Func<T>(() =>
            {
                thread.Join();
                return t;
            });
        }
View Code

 四、Thread總結

  1.大家是不是覺得多執行緒很酷呢?哈哈我剛剛學的時候也是激動的心顫抖的手。當然文章中我們介紹了很多API的使用,大家可以動手試試,API的使用是小事,最重要的是我們的思路,到我們看到回撥封裝和返回值封裝,我們都是利用了多執行緒的一些特性,來完成的這些功能拓展的。我們巨集觀的看多執行緒感覺很恐怖,但是在我們做回撥函式的時候是不是感覺有一種微觀看法,執行緒執行的內部也是同步的執行哪些方法的。好了今天就寫到這裡昨天晚上9點多就睡了,早起擼個文章美滋滋。當然多執行緒還有講完的,才說道了.NET 1.0哈哈,後續的文章也會寫出