[深入學習C#]C#實現多執行緒的方法:執行緒(Thread類)和執行緒池(ThreadPool)

詩人江湖老發表於2015-07-14

簡介

  使用執行緒的主要原因:應用程式中一些操作需要消耗一定的時間,比如對檔案、資料庫、網路的訪問等等,而我們不希望使用者一直等待到操作結束,而是在此同時可以進行一些其他的操作。
  這就可以使用執行緒來實現。
  本文主要介紹關於Thread和ThreadPool的基礎知識。

Thread類

  基本用法

  使用Thread類可以建立和控制執行緒,在下面的示例程式碼中,Thread類的建構函式過載為接受ThreadStart和ParameterizedThreadStart型別的委託引數。ThreadStart委託定義了一個返回型別為void的無引數方法,在建立Thread物件後,就可以用Start()方法啟動執行緒。

using System;
using System.Threading;

namespace ThreadDemo
{
    class Program
    {
        static void Main()
        {
            var t1 = new Thread(ThreadMain);
            t1.Start();
            Console.WriteLine("This is the main thread.");
        }
        static void ThreadMain()
        {
            Console.WriteLine("Running in a thread.");
        }
    }
}

  程式執行結果,得到兩個執行緒的輸出:
  This is the main thread.
  Running in a thread.
  我們知道,我們並不能保證那個結果先輸出,這由作業系統排程決定。

  在前文中,我們探討了將Lambda表示式和非同步委託結合使用,這裡我們也使用Lambda表示式與來給Thread類建構函式傳遞引數:
  

using System.Threading;

namespace ThreadDemo
{
    class Program
    {
        static void Main()
        {
            var t1 = new Thread(()=>Console.WriteLine("Running in a thread, id : {0}",Thread.CurrentThread.ManagedThreadId));
            t1.Start();
            Console.WriteLine("This is the main thread, id : {0}",Thread.CurrentThread.ManagedThreadId);
        }
    }
}

  在應用程式的輸出中,我們可以看到執行緒id,當然,每次執行的結果不一定一樣,因為系統每次分配的執行緒是獨立的。
  This is the main thread, id : 1.
  Running in a thread, id : 3.

  給執行緒傳遞資料

  前面的例子中,我們開啟的新執行緒只是執行了一個簡單的輸出指令,並沒有執行緒中的方法賦予引數。現在我們來探討如何給執行緒傳遞引數,也就是傳遞資料。
  一種方式是使用帶ParameterizedThreadStart委託引數的Thread建構函式;
  另一種方法是將自定義的方法傳遞給執行緒,然後啟動執行緒。

  
  ● 方法一:使用ParameterizedThreadStart委託。
  該委託的例項方法必須帶有一個object引數,而且返回型別為void。
  假設該委託例項方法如下:

static void ThreadMainWithParameters(object o)
{
    ...
    ...
}

  那麼我可以這樣開啟執行緒:

var o = new object();
var t1 = new Thread(ThreadMainWithParameters);
t1.Start(o);

  ● 方法二:使用自定義方法。
  假設我們有一個MyThread類,該類有一個方法ThreaMain():

public class MyThread
{
    ...
    ...
    public void ThreadMain()
    {
        ...
        ...
    }
    ...
    ...
}

  開啟執行緒方法如下: 

var o = new MyThread();
var t1 =new Thread(o.ThreadMain);
t1.Start();

執行緒池

  我們知道,執行緒的建立需要時間。 如果有不同的小任務要完成,我們就可以事先建立許多執行緒, 在應完成這些任務時發出請求。 這個執行緒數最好在需要更多的執行緒時增加,在需要釋放資源時減少。
  這些執行緒就是放線上程池中,C#為我們提供了一個管理執行緒池的類:ThreadPool。
  它會在需要的時候增減執行緒池中的執行緒數,如果執行緒池中執行緒數到達上限,新的作業就需要排隊等待其他執行緒完成其任務。
  
  下面的示例應用程式首先要讀取工作執行緒和 I/O執行緒的最大執行緒數,把這些資訊寫入控制檯中。接著在for迴圈中,呼叫 ThreadPool.QueueUserWorkItem()方法,傳遞一個WaitCallBack型別的委託,把 JobForThread()方法賦予執行緒池中的執行緒。執行緒池收到這個請求後,就會從池中選擇一個執行緒,來呼叫該方法。 如果執行緒池還沒有執行,就會建立一個執行緒池,並啟動第一個執行緒。 如果執行緒池己經在執行,且有一個空閒執行緒來完成該任務,就把該作業傳遞給這個執行緒。
  

using System;
using System.Threading;

namespace ThreadDemo
{
    class program
    {
        static void Main()
        {
            int nWorkThreads;
            int nCompletionPortThreads;
            ThreadPool.GetMaxThreads(out nWorkThreads, out nCompletionPortThreads);
            Console.WriteLine("Max worker threads: {0}, I/O completion threads: {1}",nWorkThreads, nCompletionProtThreads);
            for(int i = 0; i < 5; i++)
            {
                ThreadPool.QueueUserWorkItem(JobForAThread);
            }
            Thread.Sleep(3000);
        }

        static void JobForAThread(object state)
        {
            for(int i = 0; i < 3; i++)
            {
                Console.WriteLine("loop {0}, running inside pooled thread {1}", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(50);
            }
        }
    }
}

  讀者執行該程式的結果可能與此不同,也可以改變作業的睡眠時間和要處理的作業數,得到完全不同的結果。
  這裡寫圖片描述

  執行緒池使用起來很簡單,但它有一些限制 :
  ● 執行緒池中的所有執行緒都是後臺執行緒 。 如果程式的所有前臺執行緒都結束了,所有的後臺執行緒就會停止。 不能把入池的執行緒改為前臺執行緒 。
  ● 不能給入池的執行緒設定優先順序或名稱。
  ● 對於 COM對 象,入池的所有執行緒都是多執行緒單元(multit-threaded apartment , MTA)執行緒。 許 COM物件都需要單執行緒單元(single-threaded apartment , STA)線 程。
  ● 入池的執行緒只能用於時間較短的任務。 如果執行緒要一直執行(如Word的拼寫檢查器執行緒),就應使用Thread類建立一個執行緒。

相關文章