C# 多線程系列(三)
線程池
創建線程需要時間,如果有不同的小任務要完成,就可以事先創建許多線程,在應完成這些任務時發出請求。這個線程數最好在需要更多線程時增加,在需要釋放資源時減少。
不需要自己創建這樣的一個列表。該列表由ThreadPool類托管。該類會在需要時增加線程池中線程數,直到最大的線程數。
- 可以指定創建線程池時立即啟動的最小線程數,以及線程池中可用的最大線程數。
- 如果更多的作業要處理,線程池中的線程個數也到了極限,最新的作業就要排隊,且必須等待線程完成其作業。
- 線程池中的線程都是後臺線程,不能把入池的線程改為前臺線程。
- 不能給入池的線程設置優先級和名字。
- 對於COM對象,入池的所有線程都是多線程單元線程。許多COM對象都需要單線程單元線程。
- 入池的線程只能用於時間比較短的任務。如果線程要一直運行,就應該使用Thread類創建一個線程。
static void Main() { int nWorkerThreads; int nCompletionPorThreads; ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPorThreads); //ThreadPool.SetMaxThreads(500, 500); lib.print("Max worker threads : " + nWorkerThreads); lib.print("I/O completion threads: " + nCompletionPorThreads); for(int i=0; i<10; 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); } }
示例應用程序首先要讀取工作線程和I/O線程的最大線程數,把這些信息寫入控制臺中。接著在for循環中,調用Thread.QueueuserWorkItm()方法,傳遞一個WaitCallback類型的委托,把JobForAThread()方法賦予線程池中的線程。
線程池收到這個請求後,就會從池中選擇一個線程,來調用該方法。如果線程池還沒有運行,就會創建一個線程池,並啟動第一個線程。如果線程池己經在運行,且有一個空閑線程來完成該任務,就把該作業傳遞給這個線程。
異步委托
創建線程最簡單的方式是定義一個委托,然後異步調用它。
委托使用線程池完成異步任務,當沒有前臺線程運行時,異步委托將直接結束。
static void Main(string[] args) { Action action = new Action(() => { for(int i=0; i<100; i++) { lib.put("."); Thread.Sleep(10); } }); AsyncCallback calback = (IAsyncResult result) => { Thread.Sleep(100); lib.print(result.AsyncState); }; var rs = action.BeginInvoke(calback, "Begin Invoke");//如此,便啟動了異步委托。下面用不同方法等待異步委托完成。 //方法一,用EndInvoke方法,該方法會一直等待,直到委托完成任務為止。 action.EndInvoke(rs); //方法二,使用IAsyncResult相關聯的等待句柄。使用AsyncWaitHandle屬性可以訪問等待句柄。 //這個屬性返回一個WaitHandle類型對象,它可以等待委托線程完成其任務。參數是最長等待時間, //-1表示無限等待。如果當前實例收到信號,則返回為 true;否則為 false。 rs.AsyncWaitHandle.WaitOne(5000); //方法三,不斷檢查 while (true) { if (!rs.IsCompleted) { Thread.Sleep(50); } else { break; } } //因為callback最終是異步線程回調的,所以,如果直接退出,callback將無法打印出Begin Invoke。 Thread.Sleep(200); }
任務
System.Threading.Tasks包含的類抽象出了線程功能,在後臺使用ThreadPool。任務表示應完成的某個單元工作,這個單元工作可以在單獨的線程中運行,也可以以同步方式啟動一個任務,這需要等待主線程。使用任務不僅可以獲得一個抽象層,還可以對底層線程進行很多控制。
- 啟動任務
可以使用實例化的TaskFactory類,在其中把TaskMethod()方法傳遞給StarNew()方法,就會立刻啟動任務。也可以使用Task類的構造函數。實例化Task對象時,任務不會立即運行,而是指定Created狀態。接著調用Task類的Start()方法,來啟動任務。使用Task類時,還可以調用TunSynchronously()方法。
static void Main(string[] args) { Task t1 = new Task(DoOnFirst); t1.Start(); TaskFactory tf = new TaskFactory(); Task t2 = tf.StartNew(DoOnFirst); Task t3 = Task.Factory.StartNew(DoOnFirst); Task.WaitAll(t1, t2, t3); } static void DoOnFirst() { lib.print("Task.CurrentId :" + Task.CurrentId); lib.print("-----------"); Thread.Sleep(1000); }
- 連續的任務
連續任務通過在任務上調用ContinueWith()方法類定義。不帶TaskContinuationOptions參數,則無論前一個任務是如何結束的,後續任務都啟動。也可以用TaskContinuationOptions枚舉中的值,來指定連續任務在什麽情況下啟動。
static object o = new object(); static void Main(string[] args) { CancellationTokenSource cs = new CancellationTokenSource(); Task t1 = new Task(DoOnFirst, cs.Token); Task t2 = t1.ContinueWith(DoOnSecond, TaskContinuationOptions.OnlyOnRanToCompletion);//t1完成的情況下啟動t2 Task t3 = t1.ContinueWith(DoOnThird, TaskContinuationOptions.OnlyOnCanceled);//t1被取消的情況下啟動t3 try { t1.Start(); //cs.Cancel(); //打開註釋,取消了t1,將執行DoOnThird。 //cs.Token.ThrowIfCancellationRequested(); } catch { lib.print("t1.IsCanceled : " + t1.IsCanceled); } Console.ReadKey(); } static void DoOnFirst() { lock (o) { lib.print("Task.CurrentId :" + Task.CurrentId); lib.print("-----------"); Thread.Sleep(1000); } } static void DoOnSecond(Task t) { lock (o) { lib.print("task " + t.Id + " finished."); lib.print("this task id " + Task.CurrentId); lib.print("-----------"); Thread.Sleep(1000); } } static void DoOnThird(Task t) { lock (o) { lib.print("task " + t.Id + " Canceld."); lib.print("this task id " + Task.CurrentId); lib.print("-----------"); Thread.Sleep(1000); } }
任務層次結構
任務也可以構成一個層次結構。一個任務啟動一個新任務時,就啟動了一個父/子層次結構。
任務的結果
任務結束時,可以把一些有用的狀態信息寫到共享對象中。也可以使用返回結果的任務返回這些信息。
static void Main(string[] args) { Task<int> t1 = new Task<int>((object o)=>{return 111;}, ""); t1.Start(); t1.Wait(); lib.print(t1.Result); Task t = new Task(DoParentTask); t.Start(); }
C# 多線程系列(三)