線程的定義我想你們都有所瞭解,這裏我就再也不復述了。我這裏主要介紹.NET Framework中的線程(Thread、ThreadPool)。算法
.NET Framework中的線程分爲兩類:1.前臺線程;2.後臺線程。編程
1.前臺線程數據結構
class Program { static void Main(string[] args) { Console.WriteLine("=====Thread====="); TestThread(); Console.WriteLine("主線程執行完畢"); } public static void TestThread() { Thread thread = new Thread(PrintNum); thread.Start(); } public static void PrintNum() { Thread.Sleep(3000); for (int i = 0; i < 10; i++) Console.WriteLine(i); } }
運行結果多線程
從運行結果能夠看出,主線程雖然執行完畢了,可是並無退出程序,而是等待子線程執行完畢後,退出程序。異步
2.後臺線程ide
class Program { static void Main(string[] args) { Console.WriteLine("=====ThreadPool====="); ThreadPool.QueueUserWorkItem(new WaitCallback(PrintNum)); Console.WriteLine("主線程執行完畢"); } public static void PrintNum(object obj) { Thread.Sleep(3000); for (int i = 0; i < 10; i++) Console.WriteLine(i); } }
運行結果異步編程
從運行結果能夠看出,主線程運行完畢後,就直接退出了程序,沒有等待子線程。函數
總結:性能
1.前臺線程:主線程執行完畢後,會等待全部子線程執行完畢後,才退出程序。學習
2.後臺線程:主線程執行完畢後,直接退出程序,不論子線程是否執行完畢。
3.推薦:多線程的操做,推薦使用線程池線程而非新建線程。由於就算只是單純的新建一個線程,這個線程什麼事情也不作,都大約須要1M的內存空間來存儲執行上下文數據結構,而且線程的建立與回收也須要消耗資源,耗費時間。而線程池的優點在於線程池中的線程是根據須要建立與銷燬,是最優的存在。可是這也有個問題,那就是線程池線程都是後臺線程,主線程執行完畢後,不會等待後臺線程而直接結束程序。因此下面就要引出.NET Framework4.0提供的Task,來解決此類問題。
Task是.NET Framework4.0提供的新的操做線程池線程的封裝類。它提供:等待、終止、返回值...優化線程操做的功能。
1.定義
Task 對象是一種的中心思想 基於任務的異步編程模式 首次引入.NET Framework 4 中。 由於由執行工做 Task 對象一般上異步執行一個線程池線程而不是以同步方式在主應用程序線程中,您可使用 Status 屬性,以及 IsCanceled, ,IsCompleted, ,和 IsFaulted 屬性,以此來肯定任務的狀態。
以上MSDN中對Task的定義,從「異步執行一個線程池線程」能夠得出Task的後臺實現是經過線程池線程實現。
2.補充
Task的性能要優於ThreadPool。
1)ThreadPool的代碼將以先進先出的算法存儲在全局隊列中,而且多個工做者線程之間競爭同一個同步鎖。(這就Task性能優於ThreadPool的第一個緣由)
2)Task的代碼將以先進後出的算法存儲在本地隊列中,工做者線程執行本地隊列中的代碼沒有同步鎖的限制(這是Task性能優於ThreadPool的第二個緣由),而且當工做者線程2空閒而且工做者線程1忙碌時,工做者線程2會嘗試從工做者線程1(或者別的忙碌的工做者線程)的本地隊列尾部「偷」任務,並會獲取一個同步鎖,不過這種行爲不多發生。
3)簡單調用
class Program { static void Main(string[] args) { TestSimpleTask(); } public static void TestSimpleTask() { Console.WriteLine("=====Task====="); //直接建立 Task task2 = new Task(() => { Thread.Sleep(3000); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } }); //若是你想測試超時等待後,任務是否會繼續執行。就替換下面的代碼 task2.Start(); task2.Wait(); Console.WriteLine("主程序執行完畢"); /*測試超時 task2.Start(); task2.Wait(1000); Console.WriteLine("主程序執行完畢"); Console.ReadLine(); */ } }
task.Wati(時間);這個方法能夠肯定等待任務的執行時間,當超過規定的時間後將再也不等待,直接運行以後的代碼,可是任務的代碼仍然在後臺運行,若是想超過等待時間就中止任務的執行,你須要看下文深刻學習。(這個方法提供了極大的方便。若是在.NET Framework2.0中,實現相似的功能,須要定義一個時間全局變量,而後在線程中不斷的循環判斷。)
3.深刻學習
Task(Action, CancellationToken, TaskCreationOptions)
以上是MSDN中Task(不包含輸入參數與返回值)最複雜的構造函數,包含2個重要的參數CancellationToken、TaskCreationOptions,下面將詳細介紹CancellationToken、TaskCreationOptions的意義以及運用。
a.CancellationToken(取消標記)
該類用來檢測任務是否被取消。須要與System.Threading.CancellationTokenSource配合使用,CancellationTokenSource主動中止任務。(CancellationToken雖然有檢測任務是否中止的屬性,可是一旦CancellationTokenSource調用了Cancel()方法,那麼任務將當即中止運行,也就是說任務中的任何代碼都不會被執行)
場景:主線程拋出異常,在異常處理中中止任務的執行。
class Program { static void Main(string[] args) { TestCancellationTokenTask(); } public static void TestCancellationTokenTask() { CancellationTokenSource cts = new CancellationTokenSource(); try { Task task = Task.Factory.StartNew(() => { for (int i = 0; i < 10; i++) { //當任務取消時,這段檢測代碼將永遠不會被執行,由於任務已經被取消了 if (cts.Token.IsCancellationRequested) { Console.WriteLine("=====Task====="); Console.WriteLine("任務被取消"); break; } else { Console.WriteLine("=====Task====="); Console.WriteLine("子線程打印:{0}", i); Thread.Sleep(1000); } } }, cts.Token); for (int i = 0; i < 5; i++) { if (i == 3) { Console.WriteLine("=====Main====="); Console.WriteLine("主線程拋出異常"); throw new Exception("測試"); } Console.WriteLine("=====Main====="); Console.WriteLine("主線程打印:{0}", i); Thread.Sleep(1000); } task.Wait(); } catch { cts.Cancel(); } Console.WriteLine(cts.IsCancellationRequested); } }
注意:主線程拋出異常,不管任務是否被顯示取消,都會中止運行。
b.TaskCreationOptions(任務建立選項)
如下是MSDN中關於TaskCreationOptions的枚舉值,具體的運用仍是要根據實際狀況。下面介紹一下AttachedToParent的用法(第五、第6,實際的運用還須要多參考大神的運用)
// 默認
1.None
// 將任務放入全局隊列中(任務將以先到先出的原則被執行)
2.PreferFairness
// 告訴TaskScheduler,線程可能要「長時間運行」
3.LongRunning
// 將一個Task與它的父Task關聯
4.AttachedToParent
// Task以分離的子任務執行
5.DenyChildAttach
// 建立任務的執行操做將被視爲TaskScheduler.Default默認計劃程序
6.HideScheduler
// 強制異步執行添加到當前任務的延續任務
7.RunContinuationsAsynchronously
場景:將多個任務關聯爲父子任務
class Program { static void Main(string[] args) { TestTaskCreationOptionsTask(); } public static void TestTaskCreationOptionsTask() { StringBuilder sb = new StringBuilder(); Task parent = new Task(() => { new Task(() => { sb.Append("任務1"); }).Start(); new Task(() => { sb.Append("任務2"); }).Start(); new Task(() => { sb.Append("任務3"); }).Start(); //parent任務的調用線程中止5s,這樣「任務1」、「任務2」、「任務3」就有時間執行完畢了。 //這裏用來測試,當任務彼此之間是獨立的,那麼只有這種方式,控制檯纔會有打印。 //Thread.Sleep(5000); }); /* Task parent = new Task(() => { new Task(() => { sb.Append("任務1"); }, TaskCreationOptions.AttachedToParent).Start(); new Task(() => { Thread.Sleep(3000); sb.Append("任務2"); }, TaskCreationOptions.AttachedToParent).Start(); new Task(() => { Thread.Sleep(3000); sb.Append("任務3"); }, TaskCreationOptions.AttachedToParent).Start(); }); */ parent.Start(); parent.Wait(); Console.WriteLine(sb.ToString()); } }
說明:
1.Task的建立若是沒有TaskCreationOptions.AttachedToParent,那麼任務彼此之間是獨立的,parent任務不會等待「任務1」、「任務2」、「任務3」都執行完畢後,才認爲已經結束。
2.Task的建立若是有TaskCreationOptions.AttachedToParent,那麼父任務必須等待子任務都執行完畢後,纔會認爲任務結束。
注意:雖然「任務1」、「任務2」、「任務3」是在parent任務中建立,可是能夠分配在不一樣的線程池本地隊列中,由不一樣的線程調用而且任務間並非串行執行,而是並行執行。
c.返回值
以上介紹的全部內容,Task都沒有返回值,可是在實際運用中,Task執行完以後,返回一個值供外部代碼使用,這種狀況很常見。
class Program { static void Main(string[] args) { TestReturnValueTask(); } public static void TestReturnValueTask() { Task<int> task = new Task<int>(num => { Thread.Sleep(5000); return (int)num + 1; }, 100); task.Start(); Console.WriteLine(task.Result); } }
注意:當Task的返回值被調用時,主線程會等待Task執行完畢,纔會退出程序。因此這裏沒有調用task.Wait();
d.TaskContinuationOptions(任務延續選項)
有時候,咱們須要在一個任務結束後,執行另外一個任務。兩個任務之間有前後順序,這個時候,就須要用到TaskContinuationOptions。
如下是MSDN中關於TaskContinuationOptions的枚舉值,這裏只列出了部分(4.5新增的枚舉只根據MSDN的機器翻譯確實不太理解如何運用,仍是須要花些時間測試,這裏就不列出了,怕誤導讀者)
// 默認
1.None
// 將任務放入全局隊列中(任務將以先到先出的原則被執行)
2.PreferFairness
// 告訴TaskScheduler,線程可能要「長時間運行」,須要爲任務建立一個專用線程,而不是排隊讓線程池線程來處理
3.LongRunning
// 將一個Task與它的父Task關聯
4.AttachedToParent
// 但願執行第一個Task的線程,執行ContinueWith任務
5.ExecuteSynchronously
// 第一個任務沒有完成,執行後續任務
6.NotOnRanToCompletion
// 第一個任務沒有失敗,執行後續任務
7.NotOnFaulted
// 第一個任務沒有取消,執行後續任務
8.NotOnCanceled
// 只有當第一個任務取消,執行後續任務
9.OnlyOnCanceled
// 只有當第一個任務失敗,執行後續任務
10.OnlyOnFaulted
// 只有當第一個任務完成,執行後續任務
11.OnlyOnRanToCompletion
class Program { static void Main(string[] args) { TestContinueTask(); } public static void TestContinueTask() { /* Task<int> task = new Task<int>(num => { return (int)num + 1; }, 100); Task taskContinue = task.ContinueWith(c => { Console.WriteLine(c.Result); }, TaskContinuationOptions.OnlyOnRanToCompletion); task.Start(); taskContinue.Wait(); */ CancellationTokenSource cts = new CancellationTokenSource(); Task<int> task = new Task<int>(num => { return (int)num + 1; }, 100, cts.Token); Task taskContinue = task.ContinueWith(c => { Console.WriteLine("任務Task被取消了"); }, TaskContinuationOptions.OnlyOnCanceled); task.Start(); cts.Cancel(); taskContinue.Wait(); } }
寫到這裏,關於Task的介紹已經結束。可是Task的內容還有不少:異常處理(AggregateException)、取消通知委託(CancellationToken的Register方法)這些內容就留給讀者本身去學習了。
有時候,可能須要一次性建立多個任務,而且這些任務共享相同的狀態。那麼咱們就能夠經過任務工廠來建立。
任務工廠,顧名思義,用來建立任務的工廠。在大多數狀況下,不須要實例化一個新 TaskFactory<TResult> 實例。 可使用靜態Task<TResult>.Factory 屬性,它返回一個工廠對象,將使用默認值。 而後能夠調用其方法來啓動新任務或定義任務延續。
代碼
class Program { static void Main(string[] args) { TestTaskFactory(); } public static void TestTaskFactory() { TaskFactory<DateTime> factory = new TaskFactory<DateTime>(); Task<DateTime>[] tasks = new Task<DateTime>[] { factory.StartNew(() => { return DateTime.Now.ToUniversalTime(); }), factory.StartNew(() => { Thread.Sleep(5000); return DateTime.Now.ToUniversalTime(); }), factory.StartNew(() => { return DateTime.Now.ToUniversalTime(); }) }; StringBuilder sb = new StringBuilder(); foreach (Task<DateTime> task in tasks) sb.AppendFormat("{0}\t", task.Result); Console.WriteLine(sb.ToString()); } }
注意:任務能夠分配在不一樣的線程池本地隊列中,由不一樣的線程調用而且任務間並非串行執行,而是並行執行。
並行,讓多個線程池線程並行工做。因爲是並行執行,因此有一點須要注意:工做項彼此之間必須能夠並行執行!
class Program { static void Main(string[] args) { TestParallel(); } public static void TestParallel() { Parallel.For(0, 10, i => { Console.WriteLine(i); }); List<int> lists = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; Parallel.ForEach(lists, i => { Console.WriteLine(i); }); } }
說明:
1.Parallel.For效率高於Parallel.Foreach,因此當For與Foreach均可以時,推薦使用For。
2.上面的代碼,運行For時,你可能會發現數字是有順序的打印出來,給人一種串行執行的錯覺,你能夠斷點調試你的代碼,會發現確實有多個線程在運行代碼。
3.Parallel.For()、Parallel.Foreach()還有一些重載方法,你們能夠結合實際狀況使用,這裏就不復述了。
感謝你們的耐心閱讀。