C#線程

進程和線程編程

進程是一個系統級別的概念,用來描述一組資源和程序運行所必須的內存分配。每個進程都有一個惟一的進程標識符(PID);線程是進程的基本單元;進程的入口點建立的第一個線程被稱爲主線程;線程主要是由CPU寄存器、調用棧和線程本地存儲器(Thread Local Storage,TLS)組成的。CPU寄存器主要記錄當前所執行線程的狀態,調用棧主要用於維護線程所調用到的內存與數據,TLS主要用於存放線程的狀態信息。多線程

應用程序域(AppDomain):.Net可執行程序承載在進程中的邏輯分區;一個進程能夠包含多個應用程序域,每個應用程序域中承載一個.Net可執行程序;每個程序域都和該進程(或其它進程)中的其它的程序域徹底完全隔離開,只有使用分佈式編程協議(如WCF)才能訪問其它應用程序域中的任何數據;架構

System.AppDomain併發

一個線程能夠穿梭在多個應用程序域中,但在某個時刻,線程只會處於一個應用程序域內。 異步

上下文邊界分佈式

每一個線程都有本身的屬性,在每一個線程的內核對象之中,都包含一個上下文結構,上下文結構的存在是爲了反映在線程上一次執行時,線程CPU寄存器的狀態。在任什麼時候刻,Windows只將一個線程代碼分配給一個CPU,一個線程容許運行一個時間片,在線程的「時間片」結束以後,Windows會檢查現有全部線程內核對象,只有那些沒有在等待什麼的線程才適合調度。Windows選擇一個可調度的線程內核對象,而且換到它。異步編程

    Windows選擇一個可調度的線程有一套獨特的標準,Windows執行線程的規律和時間片沒多大的關係,線程在運行的任什麼時候刻均可以中止,而後Windows又去調度另外一個線程,你有點控制權,去控制你想運行的線程,可是這控制權很少,不控制爲好。對於線程的執行,記住一點:函數

你不能保證本身的線程一直運行,你不能阻止其餘的線程的運行。oop

線程優先級別0~31,Windows把線程用從高到低的調度方式輪流調度線程,假若有一個優先級別爲31的線程運行結束了,而後Windows會找下一個空閒的線程,若是空閒的線程中有一個級別也是31的線程,那麼Windows又會把31級別的線程交給CPU處理。spa

進程優先級類:

Windows支持6個進程優先級類:Idel,Below Normal,Normal,Above Normal,Hight和Realtime(依次向高),其中Normal是默認的進程優先級,因此它是最經常使用的。

Windows支持7個相對線程優先級:Idel,Lowest,Below Normal,Normal,Above Normal,Highest和Time-Critical。

相對線程優先級

進程優先級

 

Idle

Below Normal

Normal

Above Normal

High

Realtime

Time-critical

15

15

15

15

15

31

Highest

6

8

10

12

15

26

Above Normal

5

7

9

11

14

25

Normal

4

6

8

10

13

24

Below Normal

3

5

7

9

12

23

Lowest

2

4

6

8

11

22

Idle

1

1

1

1

1

16

記住:若是更改一個進程的優先級類,線程的相對優先級不會改變,但它的優先值會改變

Windows永遠都不會調度進程,他調度的只有線程,「進程優先級類」是Microsoft提出的一個抽象概念,目的是爲了幫助你理解本身的應用程序和其餘正在運行的應用程序的關係,它沒有別的用途。

能夠更改它的線程相對優先級,Thread中的Priority屬性,向它傳遞ThreadPriority枚舉類型中定義的5各值之一,即在上表中的灰色部分列。

多線程併發的一種形式,採用多個線程來執行程序;應用程序幾乎不須要自行建立新的線程。你若要爲 COM interop 程序建立 STA線程,就得建立線程,這是惟一須要線程的狀況。線程是低級別的抽象,線程池是稍微高級一點的抽象,當代碼段遵循線程池的規則運行時,線程池就會在須要時建立線程。

線程池ThreadPool

每一個進程都有一個線程池,線程池的默認大小爲:每一個可用的處理器有 25 個線程。使用 SetMaxThreads 方法能夠更改線程池中的線程數。

ThreadPool類型擁有一個QueueUserWorkItem的靜態方法。該靜態方法接收一個委託(WaitCallback),表明用戶自定義的一個異步操做。

//     一個 System.Threading.WaitCallback,表示要執行的方法。

//     若是此方法成功排隊,則爲 true;若是未能將該工做項排隊,則引起 System.NotSupportedException。

//System.NotSupportedException:承載公共語言運行時的宿主不支持此操做。

[SecuritySafeCritical]

public static bool QueueUserWorkItem(WaitCallback callBack);        // 將方法排入隊列以便執行,並指定包含該方法所用數據的對象。此方法在有線程池線程變得可用時執行。

  //     System.Threading.WaitCallback,它表示要執行的方法。

  //   state:   包含方法所用數據的對象。

 //     若是此方法成功排隊,則爲 true;若是未能將該工做項排隊,則引起 System.NotSupportedException。      

 [SecuritySafeCritical]

public static bool QueueUserWorkItem(WaitCallback callBack, object state);

使用線程池的好處:

減小了線程的建立、開始和中止的次數,提升了效率

可以使咱們將注意力放在業務邏輯上而不是多線程架構上。

如下場景是不適合使用線程池,而是手工管理線程:

  1. 須要前臺線程時。由於線程池中的線程老是默認爲後臺線程。
  2. 須要線程具備特定的優先級。由於放到線程池中的線程都是默認的優先級(ThreadPriority.Normal),沒法對其優先級進行設置。
  3. 須要長時間運行的任務。因爲線程池具備最大線程數限制,所以大量阻塞的線程池線程可能會阻止任務啓動。
  4. 若是須要有一個帶有固定標識的線程便於退出、掛起或經過名字發現它。
  5. 對於COM對象,入池的全部線程都是多線程單元(multithreaded apartment MTA)線程;許多COM對象都須要單線程單元(single-threaded apartment STA)線程;

協做式取消操做模式:不管執行操做的代碼,仍是試圖取消操做的代碼,都必須使用兩個類型CancellationToken CancellationTokenSource

爲取消一個操做必須先建立一個CancellationTokenSource對象,這個對象包含了和管理取消有關的全部狀態。

// 摘要:通知 System.Threading.CancellationToken,告知其應被取消。
    [ComVisible(false)]
    public sealed class CancellationTokenSource : IDisposable
    {
        // 摘要:初始化 System.Threading.CancellationTokenSource。
        public CancellationTokenSource();        
        // 返回結果:是否已請求取消此 System.Threading.CancellationTokenSource。
        public bool IsCancellationRequested { get; }
        // 返回結果:與此 System.Threading.CancellationTokenSource 關聯的 System.Threading.CancellationToken。傳遞給操做,使那些操做能夠取消;
        // 異常:T:System.ObjectDisposedException:已釋放標記源。
        public CancellationToken Token { get; }
        // 摘要:建立一個將在任何源標記處於取消狀態時處於取消狀態的 System.Threading.CancellationTokenSource。
        //   token1: 要觀察的第一個 System.Threading.CancellationToken。
        //   token2: 要觀察的第二個 System.Threading.CancellationToken。
        // 返回結果:一個連接到源標記的 System.Threading.CancellationTokenSource。
        // 異常: T:System.ObjectDisposedException:與源標記之一關聯的 System.Threading.CancellationTokenSource 已被釋放。
        public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2);
        // 摘要: 建立一個將在任何源標記處於取消狀態時處於取消狀態的 System.Threading.CancellationTokenSource。
        // tokens: 要觀察的 System.Threading.CancellationToken 實例。
        // 返回結果:一個連接到源標記的 System.Threading.CancellationTokenSource。
        // 異常: T:System.ObjectDisposedException:與源標記之一關聯的 System.Threading.CancellationTokenSource 已被釋放。T:System.ArgumentNullException: tokens 爲 null。
        public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens);
        // 摘要:傳達取消請求。
        // 異常: T:System.ObjectDisposedException:此 System.Threading.CancellationTokenSource 已被釋放。
        //   T:System.AggregateException:聚合異常包含由相關聯的 System.Threading.CancellationToken 上已註冊的回調引起的全部異常。
        public void Cancel();
        // 摘要:傳達取消請求。
        // 參數: throwOnFirstException:指定異常是否應當即傳播。
        // 異常: T:System.ObjectDisposedException:此 System.Threading.CancellationTokenSource 已被釋放。
        // T:System.AggregateException:聚合異常包含由相關聯的 System.Threading.CancellationToken 上已註冊的回調引起的全部異常。
        public void Cancel(bool throwOnFirstException);
        // 摘要:釋放 System.Threading.CancellationTokenSource 類的當前實例所使用的全部資源。
        public void Dispose();
}

Register方法傳遞一個Action的委託, 在線程中止時被回調;
private static void CancellingAWorkItem(bool isCanCancel=true) {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken noneToken = CancellationToken.None;//調用該靜態屬性,實現禁止操做被取消
        // Pass the CancellationToken and the number-to-count-to into the operation
        ThreadPool.QueueUserWorkItem(o => Count(isCanCancel==true?cts.Token:noneToken, 1000));
        Console.WriteLine("Press <Enter> to cancel the operation.");
        Console.ReadLine();
        cts.Cancel();  // If Count returned already, Cancel has no effect on it
        // Cancel returns immediately, and the method continues running here...
        Console.ReadLine();  // For testing purposes
    }
    private static void Count(CancellationToken token, Int32 countTo) {
        for (Int32 count = 0; count < countTo; count++) {
            if (token.IsCancellationRequested) {
                Console.WriteLine("Count is cancelled");
                break; // Exit the loop to stop the operation
            }            
            //token.Register(() => { Console.WriteLine("線程被終止"); }); Console.WriteLine(count); Thread.Sleep(
200); // For demo, waste some time } Console.WriteLine("Count is done"); }

線程池將本身的線程劃分爲工做者(worker)線程和I/O線程:工做者線程用於執行計算限制的異步操做,I/O線程用於異步I/O限制操做;

計算限制的異步操做:也稱併發編程。容許線程池在多個CPU內核上調度任務,使多個線程能併發工做。

I/O限制的異步操做:也稱異步編程。容許將任務交與硬件設備處理,期間不徹底佔用線程和CPU資源。而後由線程池線程處理I/O操做結果;

當咱們調用Windows API對I/O如文件進行同步讀寫時,該線程建立一個IRP的設備請求,並將IRP發送給device stack,而後在覈心態等待其完成。而當咱們用異步方式時,該線程發送完IRP後,則返回,繼續後續的操做。Windows有不少種方式能夠通知該I/O操做的完成,與Thread Pool相關的有兩種。一種是將完成notification放在該線程的APC隊列中,該隊列只有當線程進入等待狀態是,纔會被讀取;而另外一種方式則是I/O Completion Port,咱們能夠把這樣也認爲是個隊列,而讀取這個隊列能夠經過GetQueuedCompletionStatus API函數。

在.Net線程池中,I/O Thread實際就是I/O完成端口,而Worker Thread能夠當作.Net經過Thread類預先建立的一組線程。.Net及ThreadPool類中提供的方法,如QueueUserWorkItem, Timer, delegate回調等使用的都是Worker Thread。而.Net中對I/O操做的封裝,如FileStream, NetworkStream等則是使用的IO Thread。

C#中提到的計算約束Computing-Bound和I/O約束I/O-Bound的操做。當咱們調用FileStream.BeginRead讀文件時,BeginRead並無建立新的線程去執行讀操做,讀操做(IRP)被設備執行,同時該線程繼續執行其餘任務。而當設備完成讀操做後,線程池(IO Thread)中的一個線程開始執行回調函數。

而當咱們執行的是Computing-Bound的操做時,開始咱們就新建立一個線程或經過ThreadPool.QueueUserWorkItem使用線程池中的線程來執行操做,任務完成後,這個線程會執行回調函數。

相關文章
相關標籤/搜索