C#多線程學習


一、線程的定義

  進程(Process)是Windows系統中的一個基本概念,它包含着一個運行程序所需要的資源。進程之間是相對獨立的,一個進程無法訪問另一個進程的數據(除非利用分布式計算方式),一個進程運行的失敗也不會影響其他進程的運行,Windows系統就是利用進程把工作划分為多個獨立的區域的。進程可以理解為一個程序的基本邊界

  線程(Thread)進程中的基本執行單元,在進程入口執行的第一個線程被視為這個進程的主線程。在.NET應用程序中,都是以Main()方法作為入口的,當調用此方法時系統就會自動創建一個主線程。線程主要是由CPU寄存器、調用棧和線程本地存儲器(Thread Local Storage,TLS)組成的。CPU寄存器主要記錄當前所執行線程的狀態,調用棧主要用於維護線程所調用到的內存與數據,TLS主要用於存放線程的狀態信息。

  多線程,在單CPU系統的一個單位時間( time slice)內,CPU只能運行單個線程運行順序取決於線程的優先級別。如果在單位時間內線程未能完成執行,系統就會把線程的狀態信息保存到線程的本地存儲器(TLS)中,以便下次執行時恢復執行。而多線程只是系統帶來的一個假象,它在多個單位時間內進行多個線程的切換。因為切換頻密而且單位時間非常短暫,所以多線程可以被視作同時運行。

  適當使用多線程能提高系統的性能,比如:在系統請求大容量的數據時使用多線程,把數據輸出工作交給異步線程,使主線程保持其穩定性去處理其他問題。但需要注意一點,因為CPU需要花費不少的時間在線程的切換上,所以過多地使用多線程反而會導致性能下降。(用量要適中)

二、線程的基礎知識

  2.1 System.Threading.Thread類

System.Threadubg.Thread是用於控制線程的基礎類,通過Thread可以控制當前醫用程序域中線程的創建、掛起、停止、銷毀。它包括以下常用公共屬性:

   屬性名稱:

  CurrentContext: 獲取線程正在其中執行的當前上下文

  CurrentThread: 獲取當前正在運行的線程

  ExecutionContext: 獲取一個ExecutionContext對象,該對象包含有關當前線程的各種上下文的信息。

  IsAlive: 獲取一個值,該值指示當前線程的執行狀態

  IsBackground: 獲取或設置一個值,該值指示某個線程是否為后台線程

  IsThreadPoolThread: 獲取一個值,該值指示線程是否屬於托管線程池

  ManagedThreadId: 獲取當前托管線程的唯一標識符

  Name: 獲取或設置線程的名稱

  Priority: 獲取或設置一個值,該值指示線程的調度優先級

  ThreadState: 獲取一個值,該值包含當前線程的狀態

  2.1.1線程的標識符

  ManagedThreadId是確認線程的唯一標識符,程序在大部分情況下都是通過Thread.ManagedThreadId來辨別線程的。而Name是一個可變值,在默認時候,Name為一個空值Null,開發人員可以通過程序設置線程的名字,但這知識一個輔助功能。

  2.1.2線程的優先級別

  .NET為線程設置Priority屬性來定義線程執行的優先級別。里面包含5個選項,其中NORMAL是默認值。除非系統有特殊要求,否則不應該隨便設置線程的優先級別。

  成員名稱

  Lowerst: 可以將Thread安排在具有任何其他優先級的線程之后

  BelowNormal: 可以將Thread安排在具有Normal優先級的線程之后,在具有Lowest優先級的線程之前

  Normal: 默認選擇。可以將Thread安排在具有AboveNormal優先級的線程之后,在具有BelowNormal優先級的線程之前

  AboveNormal: 可以將Thread安排在具有Highest優先級的線程之后,在具有Normal優先級的線程之前

  Highest:可以將Thread安排在具有任何其他優先級的線程之前

  2.1.3 線程的狀態

  通過ThreadState可以檢測線程使處於Unstarted、sleeping、running等等狀態,它比IsAlive屬性能提供更多的特定信息。前面說過,一個應用程序中可能包括多個上下文,而通過CurrentContext可以獲取線程當前的上下文,CurrentThread是最常用的一個屬性,它是用於獲取當前運行的線程。

  2.1.4 System.Threading.Thread的方法

  Thread中包括了多個方法來控制線程的創建、掛起、停止、銷毀,以后來的例子中會經常使用。

  方法名稱

  Abort(): 終止本線程

  GetDomain(): 返回當前線程正在其中運行的當前域

  GetDomainId(): 返回當前線程正在其中運行的當前域Id

  Interrupt(): 中斷處於WaitSleepJoin線程狀態的線程

  Join(): 已重載。阻塞調用線程,直到某個線程終止時為止(讓某個線程運行結束再開始執行其他的線程)

  Resume(): 繼續運行已掛起的線程

  Start(): 執行本線程

  Suspend(): 掛起當前的線程,如果當前線程屬於掛起狀態則此不起作用

  Sleep(): 把正在運行的線程掛起一段時間

  2.1.5 開發實例 

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 namespace ConsoleApplication1
 7 {
 8     class Program
 9     {
10         static void Main(string[] args)
11         {
12             Thread thread = Thread.CurrentThread;
13             thread.Name = "Main Thread ,Hello!";
14             string threadMessage = string.Format("Thread ID:{0}\n"+"Current AppDomainId:{1}\n"+
15                 "Current ContextId:{2}\n"+"Thread Name:{3}\n"+
16                 "Thread State:{4}\n"+"Thread Priority:{5}\n",thread.ManagedThreadId,Thread.GetDomainID(),Thread.CurrentContext.ContextI            D,thread.Name,thread.ThreadState,thread.Priority);
18             Console.WriteLine(threadMessage);
19             Console.ReadKey();
20            
21         }
22     }
23 }

  運行結果:

  

2.2 System.Threading 命名空間

  在System.Threading命名空間內提供多少個方法來構建多線程應用程序,其中ThreadPool與Thread是多線程開發中最常用到的,在.NET中專門設定了一個CLR線程池專門用於管理線程的運行,這個CLR線程池證實通過ThreadPool類來管理。而Thread是管理線程的最直接方式,下面幾節將詳細介紹有關內容。

說明
AutoResetEvent 通知正在等待的線程已發生事件。無法繼承此類。
ExecutionContext 管理當前線程的執行上下文。無法繼承此類。
Interlocked 為多個線程共享的變量提供原子操作
Monitor         提供同步對對象的訪問的機制
Mutex 一個同步基元,也可用於進程間同步
Thread 創建並控制線程,設置其優先級並獲取其狀態
ThreadAbortException 在對Abort方法進行調用時引發的異常。無法繼承此類
ThreadPool 提供一個線程池,該線程也可用於發送工作項、處理異步I/O、代表其他線程等待以及處理計時器
Timeout 包含用於指定無限長的時間的常數。無法繼承此類
Timer 提供以指定的時間間隔執行方法的機制。無法繼承此類
WaitHandle 封裝等待對共享資源的獨占訪問的操作系統特定的對象

 

 

 

 

 

  

 

  

 2.3 線程的管理方式

通過ThreadStart來創建一個新的線程是最直接的方法,但這樣創建出來的線程比較難管理,如果創建過多的線程反而會讓系統的性能下降。有見及此,NET為線程管理專門設置了一個CLR線程池,使用CLR線程池系統可以更合理地管理線程的使用。所有請求的服務都能運行與線程池中,當運行結束時線程便會回歸到線程池。通過設置,能控制線程池的最大線程數量,在請求超出線程最大值時,線程池能按照操作的優先級基礎知識就為大家介紹到這里,下面將消息介紹多線程的開發。

 

三、以ThreadStart方式實現多線程

3.1 使用ThreadStart委托 

這里先以一個例子體現以下多線程帶來的好處,首先在Message類中建立一個方法ShowMessage(),里面顯示了當前運行線程的Id,並使用Thread.Sleep(int)方法模擬部分工作。在main()中通過ThreadStart委托綁定Message對象的ShowMessage()方法,然后通過Thread.Start()執行異步方法。

  

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
    public class Message
    {
       public void ShowMessage1()
       {
           string message = string.Format("Async1 threadId is :{0}", Thread.CurrentThread.ManagedThreadId);
           Console.WriteLine(message);

           for(int n = 0;n < 12;n++)
           {
               Thread.Sleep(500);
               Console.WriteLine("The number1 is:"+n.ToString());
           }
           
       }
       public void ShowMessage2()
       {
           string message = string.Format("Async2 threadId is:{0}", Thread.CurrentThread.ManagedThreadId);
           Console.WriteLine(message);
           for (int n = 0; n < 12; n++)
           {
               Thread.Sleep(500);
               Console.WriteLine("The number2 is:" + n.ToString());
           }
          
       }
    }
    class Program
    {
        static void Main(string[] args)//主線程
        {
            Console.WriteLine("Main threadId is:" + Thread.CurrentThread.ManagedThreadId);
            Message message = new Message();
            Thread thread1 = new Thread(new ThreadStart(message.ShowMessage1));//ThreadStart:在線程上執行方法
            Thread thread2 = new Thread(new ThreadStart(message.ShowMessage2));
            thread1.Start();//啟動子線程
            thread2.Start();
            Console.WriteLine("Do something......!");
            Console.WriteLine("Main thread working is complete!");
            Console.ReadKey();//為了讓控制台停住而加,主線程運行到這一句停住,等待用戶的輸入任意鍵結束主線程
        }
    }
}

得到下面的運行結果

  注意運行結果,在調用Thread.Start()方法后,系統以異步方式運行Message.ShowMessage1()和Message.ShowMessage2(),  而主線程的操作是繼續執行的,在Message.ShowMessage1()和Message.ShowMessage2()完成前,主線程已經完成所有輸出的操作,進入了等待用戶輸入的狀態(主程序最后一句Console.ReadKey()的作用),如果你在兩個子線程沒結束之前用戶按下了任意鍵,會發現窗口也不會停留,因為由於你的按鍵主線程已經提早結束了。

  多線程的存在,讓程序至少看上去不是按順序執行,仿佛是多個程序在同時進行。

3.2 使用ParameterizedThreadStart委托

  ParameterizedThreadStart委托與ThreadStart委托非常相似,但ParameterizedThreadStart委托是面向帶參數方法的。注意ParameterizeThreadStart對應方法(放在線程里准備運行的方法)的參數為object,此參數可以為一個值對象,也可以為一個自定義對象。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
    public class Person
    {
        public string Name
        {
            get;
            set;

        }
        public int Age
        {
            get;
            set;
        }
    }
    public class Message
    {
       public void ShowMessage(object person)
       {
           if (person != null)
           {
               Person _person = (Person)person;
               string message = string.Format("\n{0}'s age is {1}!\nAsync threadId is:{2}", _person.Name,
                   _person.Age, Thread.CurrentThread.ManagedThreadId);
               Console.WriteLine(message);
           }
           for(int n = 0;n < 12;n++)
           {
               Thread.Sleep(500);
               Console.WriteLine("The number1 is:"+n.ToString());
           }
           
       }
    
    }
    class Program
    {
        static void Main(string[] args)//主線程
        {
            Console.WriteLine("Main threadId is:" + Thread.CurrentThread.ManagedThreadId);
            Message message = new Message();
            Thread thread = new Thread(new ParameterizedThreadStart(message.ShowMessage));//ThreadStart:在線程上執行方法
            Person person = new Person();
            person.Name = "Jack";
            person.Age = 21;
            thread.Start(person);//啟動子線程
           
            Console.WriteLine("Do something......!");
            Console.WriteLine("Main thread working is complete!");
            Console.ReadKey();
        }
    }
}

運行結果如下

 

3.3 前台線程與后台線程

在以上兩個例子中主線程最后一句程序是Console.ReadKey(),這樣窗口可以在子線程結束后停住,如果兩個例子都沒有這句程序的話,可以發現系統依然會等待異步線程完成后才會結束。這是因為使用Thread.Start()啟動的線程默認為前台線程,而系統必須等待所有前台線程運行結束后,應用程序域才會自動卸載。

在第二節曾將介紹過線程Thread有一個屬性IsBacground,通過把此屬性設置為true,就可以把線程設置為后台線程!這時應用程序域將在主線程完成時就被卸載,而不會等待異步線程的運行。

3.4 掛起線程

為了等待其他后台線程完成后再結束主線程,就可以使用Thread.Sleep()方法

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 namespace ConsoleApplication1
 7 {
 8     public class Message
 9     {
10         public void ShowMessage()
11         {
12             string message = string.Format("\nAsync threadId is :{0}", Thread.CurrentThread.ManagedThreadId);
13             Console.WriteLine(message);
14             for (int n = 0; n < 10; n++)
15             {
16                 Thread.Sleep(100);
17                 Console.WriteLine("The number is:" + n.ToString());
18             }
19         }
20     }
21     class Program
22     {
23         static void Main(string[] args)//主線程
24         {
25             Console.WriteLine("Main threadId is:" + Thread.CurrentThread.ManagedThreadId);
26             Message message = new Message();
27             Thread thread = new Thread(new ThreadStart(message.ShowMessage));
28             thread.IsBackground = true;
29             thread.Start();
30 
31             Console.WriteLine("Do something ..........!");
32             Console.WriteLine("Main thread working is complete!");
33             Console.WriteLine("Main thread sleep!");
34             Thread.Sleep(5000);
35         }
36 
37     }
38 }

運行結果

3.5 Suspend與Resume(慎用)

Thread.Suspend()與Thread.Resume()是在Framework1.0就已經存在的老方法了,它們分別可以掛起、恢復線程。但在Framework2.0中就已經明確排斥這兩個方法。這是因為一旦某個線程占用了已有的資源,再使用Suspend()使線程長期處於掛起狀態,當在其他線程調用這些資源的時候就會引起死鎖!所以在沒有必要的情況下應該避免使用這兩個方法。

3.6 終止線程

若想終止正在運行的線程,可以使用Abort()方恢復線程的執行,可以在捕獲異常后,在catch(ThreadAbortException ex){...}中調用Thread.ResetAbort()取消終止

而使用Thread.Join()可以保證應用程序域等待異步線程結束后才終止運行。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
   
    class Program
    {
        static void Main(string[] args)//主線程
        {
            Console.WriteLine("Main threadId is:" + Thread.CurrentThread.ManagedThreadId);
            Thread thread = new Thread(new ThreadStart(AsyncThread));
            thread.IsBackground = true;
            thread.Start();
            thread.Join();//知道thread線程結束再運行別的線程
            Console.WriteLine("子線程終於運行完了,輪到我主線程啦,我的線程唯一標識號: " + Thread.CurrentThread.ManagedThreadId);
            Console.ReadKey();
        }
        static void AsyncThread()
        {
            try
            {
                string message = string.Format("\nAsync threaddId is:{0}", Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine(message);
                for (int n = 0; n < 10; n++)
                {
                    if (n >= 4)
                    {
                        //n = 4時,終止線程
                        Thread.CurrentThread.Abort(n);

                    }
                    Thread.Sleep(500);
                    Console.WriteLine("the number is:" + n.ToString());
                }

            }
            catch (ThreadAbortException ex)
            {
                //輸出終止線程時n的值
                if (ex.ExceptionState != null)
                    Console.WriteLine(string.Format("Thread abort when the number is:{0}",ex.ExceptionState.ToString()));
                //取消終止,繼續執行線程
                Thread.ResetAbort();
                Console.WriteLine("Thread ResetAbort!");

            }
            //線程結束
            Console.WriteLine("Thread Close!");
        }
    }
}

運行結果

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM