线程(Thread,ThreadPool)、Task、Parallel
線程(Thread、ThreadPool)
線程的定義我想大家都有所了解,這里我就不再復(fù)述了。我這里主要介紹.NET Framework中的線程(Thread、ThreadPool)。
.NET Framework中的線程分為兩類:1.前臺線程;2.后臺線程。
1.前臺線程
class Program {static void Main(string[] args){Console.WriteLine("=====Thread=====");TestThread();Console.WriteLine("主線程執(zhí)行完畢"); }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);} }運行結(jié)果
從運行結(jié)果可以看出,主線程雖然執(zhí)行完畢了,但是并沒有退出程序,而是等待子線程執(zhí)行完畢后,退出程序。
2.后臺線程
class Program {static void Main(string[] args){Console.WriteLine("=====ThreadPool=====");ThreadPool.QueueUserWorkItem(new WaitCallback(PrintNum));Console.WriteLine("主線程執(zhí)行完畢"); }public static void PrintNum(object obj){Thread.Sleep(3000);for (int i = 0; i < 10; i++)Console.WriteLine(i);} }?運行結(jié)果
從運行結(jié)果可以看出,主線程運行完畢后,就直接退出了程序,沒有等待子線程。
總結(jié):
1.前臺線程:主線程執(zhí)行完畢后,會等待所有子線程執(zhí)行完畢后,才退出程序。
2.后臺線程:主線程執(zhí)行完畢后,直接退出程序,不論子線程是否執(zhí)行完畢。
3.推薦:多線程的操作,推薦使用線程池線程而非新建線程。因為就算只是單純的新建一個線程,這個線程什么事情也不做,都大約需要1M的內(nèi)存空間來存儲執(zhí)行上下文數(shù)據(jù)結(jié)構(gòu),并且線程的創(chuàng)建與回收也需要消耗資源,耗費時間。而線程池的優(yōu)勢在于線程池中的線程是根據(jù)需要創(chuàng)建與銷毀,是最優(yōu)的存在。但是這也有個問題,那就是線程池線程都是后臺線程,主線程執(zhí)行完畢后,不會等待后臺線程而直接結(jié)束程序。所以下面就要引出.NET Framework4.0提供的Task,來解決此類問題。
Task
Task是.NET Framework4.0提供的新的操作線程池線程的封裝類。它提供:等待、終止、返回值...優(yōu)化線程操作的功能。
1.定義
Task 對象是一種的中心思想 基于任務(wù)的異步編程模式 首次引入.NET Framework 4 中。 因為由執(zhí)行工作 Task 對象通常上異步執(zhí)行一個線程池線程而不是以同步方式在主應(yīng)用程序線程中,您可以使用 Status 屬性,以及 IsCanceled, ,IsCompleted, ,和 IsFaulted 屬性,以此來確定任務(wù)的狀態(tài)。?
以上MSDN中對Task的定義,從“異步執(zhí)行一個線程池線程”可以得出Task的后臺實現(xiàn)是通過線程池線程實現(xiàn)。
2.補充
Task的性能要優(yōu)于ThreadPool。
1)ThreadPool的代碼將以先進先出的算法存儲在全局隊列中,并且多個工作者線程之間競爭同一個同步鎖。(這就Task性能優(yōu)于ThreadPool的第一個原因)
2)Task的代碼將以先進后出的算法存儲在本地隊列中,工作者線程執(zhí)行本地隊列中的代碼沒有同步鎖的限制(這是Task性能優(yōu)于ThreadPool的第二個原因),并且當(dāng)工作者線程2空閑并且工作者線程1忙碌時,工作者線程2會嘗試從工作者線程1(或者別的忙碌的工作者線程)的本地隊列尾部“偷”任務(wù),并會獲取一個同步鎖,不過這種行為很少發(fā)生。
3)簡單調(diào)用
class Program {static void Main(string[] args){TestSimpleTask();}public static void TestSimpleTask(){Console.WriteLine("=====Task=====");//直接創(chuàng)建Task task2 = new Task(() => {Thread.Sleep(3000);for (int i = 0; i < 10; i++){Console.WriteLine(i);}});//如果你想測試超時等待后,任務(wù)是否會繼續(xù)執(zhí)行。就替換下面的代碼 task2.Start();task2.Wait();Console.WriteLine("主程序執(zhí)行完畢");/*測試超時task2.Start();task2.Wait(1000);Console.WriteLine("主程序執(zhí)行完畢");Console.ReadLine();*/} }task.Wati(時間);這個方法可以確定等待任務(wù)的執(zhí)行時間,當(dāng)超過規(guī)定的時間后將不再等待,直接運行之后的代碼,但是任務(wù)的代碼仍然在后臺運行,如果想超過等待時間就停止任務(wù)的執(zhí)行,你需要看下文深入學(xué)習(xí)。(這個方法提供了極大的方便。如果在.NET Framework2.0中,實現(xiàn)類似的功能,需要定義一個時間全局變量,然后在線程中不斷的循環(huán)判斷。)
3.深入學(xué)習(xí)
Task(Action, CancellationToken, TaskCreationOptions)?
以上是MSDN中Task(不包含輸入?yún)?shù)與返回值)最復(fù)雜的構(gòu)造函數(shù),包含2個重要的參數(shù)CancellationToken、TaskCreationOptions,下面將詳細(xì)介紹CancellationToken、TaskCreationOptions的意義以及運用。
a.CancellationToken(取消標(biāo)記)
該類用來檢測任務(wù)是否被取消。需要與System.Threading.CancellationTokenSource配合使用,CancellationTokenSource主動停止任務(wù)。(CancellationToken雖然有檢測任務(wù)是否停止的屬性,但是一旦CancellationTokenSource調(diào)用了Cancel()方法,那么任務(wù)將立即停止運行,也就是說任務(wù)中的任何代碼都不會被執(zhí)行)
場景:主線程拋出異常,在異常處理中停止任務(wù)的執(zhí)行。
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++){//當(dāng)任務(wù)取消時,這段檢測代碼將永遠(yuǎn)不會被執(zhí)行,因為任務(wù)已經(jīng)被取消了if (cts.Token.IsCancellationRequested){Console.WriteLine("=====Task=====");Console.WriteLine("任務(wù)被取消");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);} }注意:主線程拋出異常,無論任務(wù)是否被顯示取消,都會停止運行。
b.TaskCreationOptions(任務(wù)創(chuàng)建選項)
以下是MSDN中關(guān)于TaskCreationOptions的枚舉值,具體的運用還是要根據(jù)實際情況。下面介紹一下AttachedToParent的用法(第5、第6,實際的運用還需要多參考大神的運用)
// 默認(rèn) 1.None // 將任務(wù)放入全局隊列中(任務(wù)將以先到先出的原則被執(zhí)行) 2.PreferFairness// 告訴TaskScheduler,線程可能要“長時間運行” 3.LongRunning// 將一個Task與它的父Task關(guān)聯(lián) 4.AttachedToParent// Task以分離的子任務(wù)執(zhí)行 5.DenyChildAttach// 創(chuàng)建任務(wù)的執(zhí)行操作將被視為TaskScheduler.Default默認(rèn)計劃程序 6.HideScheduler// 強制異步執(zhí)行添加到當(dāng)前任務(wù)的延續(xù)任務(wù) 7.RunContinuationsAsynchronously場景:將多個任務(wù)關(guān)聯(lián)為父子任務(wù) class Program {static void Main(string[] args){TestTaskCreationOptionsTask();}public static void TestTaskCreationOptionsTask(){StringBuilder sb = new StringBuilder();Task parent = new Task(() =>{new Task(() => {sb.Append("任務(wù)1");}).Start();new Task(() => {sb.Append("任務(wù)2");}).Start();new Task(() => {sb.Append("任務(wù)3");}).Start();//parent任務(wù)的調(diào)用線程停止5s,這樣“任務(wù)1”、“任務(wù)2”、“任務(wù)3”就有時間執(zhí)行完畢了。//這里用來測試,當(dāng)任務(wù)彼此之間是獨立的,那么只有這種方式,控制臺才會有打印。//Thread.Sleep(5000); });/*Task parent = new Task(() =>{new Task(() => {sb.Append("任務(wù)1");}, TaskCreationOptions.AttachedToParent).Start();new Task(() => {Thread.Sleep(3000);sb.Append("任務(wù)2");}, TaskCreationOptions.AttachedToParent).Start();new Task(() => {Thread.Sleep(3000);sb.Append("任務(wù)3");}, TaskCreationOptions.AttachedToParent).Start();});*/parent.Start();parent.Wait();Console.WriteLine(sb.ToString());} }說明:
1.Task的創(chuàng)建如果沒有TaskCreationOptions.AttachedToParent,那么任務(wù)彼此之間是獨立的,parent任務(wù)不會等待“任務(wù)1”、“任務(wù)2”、“任務(wù)3”都執(zhí)行完畢后,才認(rèn)為已經(jīng)結(jié)束。
2.Task的創(chuàng)建如果有TaskCreationOptions.AttachedToParent,那么父任務(wù)必須等待子任務(wù)都執(zhí)行完畢后,才會認(rèn)為任務(wù)結(jié)束。
注意:雖然“任務(wù)1”、“任務(wù)2”、“任務(wù)3”是在parent任務(wù)中創(chuàng)建,但是可以分配在不同的線程池本地隊列中,由不同的線程調(diào)用并且任務(wù)間并不是串行執(zhí)行,而是并行執(zhí)行。
c.返回值
以上介紹的所有內(nèi)容,Task都沒有返回值,但是在實際運用中,Task執(zhí)行完之后,返回一個值供外部代碼使用,這種情況很常見。
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);} }注意:當(dāng)Task的返回值被調(diào)用時,主線程會等待Task執(zhí)行完畢,才會退出程序。所以這里沒有調(diào)用task.Wait();
d.TaskContinuationOptions(任務(wù)延續(xù)選項)
有時候,我們需要在一個任務(wù)結(jié)束后,執(zhí)行另一個任務(wù)。兩個任務(wù)之間有先后順序,這個時候,就需要用到TaskContinuationOptions。
以下是MSDN中關(guān)于TaskContinuationOptions的枚舉值,這里只列出了部分(4.5新增的枚舉只根據(jù)MSDN的機器翻譯確實不太理解如何運用,還是需要花些時間測試,這里就不列出了,怕誤導(dǎo)讀者)
// 默認(rèn) 1.None// 將任務(wù)放入全局隊列中(任務(wù)將以先到先出的原則被執(zhí)行) 2.PreferFairness// 告訴TaskScheduler,線程可能要“長時間運行”,需要為任務(wù)創(chuàng)建一個專用線程,而不是排隊讓線程池線程來處理 3.LongRunning// 將一個Task與它的父Task關(guān)聯(lián) 4.AttachedToParent// 希望執(zhí)行第一個Task的線程,執(zhí)行ContinueWith任務(wù) 5.ExecuteSynchronously// 第一個任務(wù)沒有完成,執(zhí)行后續(xù)任務(wù) 6.NotOnRanToCompletion// 第一個任務(wù)沒有失敗,執(zhí)行后續(xù)任務(wù) 7.NotOnFaulted// 第一個任務(wù)沒有取消,執(zhí)行后續(xù)任務(wù) 8.NotOnCanceled// 只有當(dāng)?shù)谝粋€任務(wù)取消,執(zhí)行后續(xù)任務(wù) 9.OnlyOnCanceled// 只有當(dāng)?shù)谝粋€任務(wù)失敗,執(zhí)行后續(xù)任務(wù) 10.OnlyOnFaulted// 只有當(dāng)?shù)谝粋€任務(wù)完成,執(zhí)行后續(xù)任務(wù) 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("任務(wù)Task被取消了");}, TaskContinuationOptions.OnlyOnCanceled);task.Start();cts.Cancel();taskContinue.Wait();} }寫到這里,關(guān)于Task的介紹已經(jīng)結(jié)束。但是Task的內(nèi)容還有很多:異常處理(AggregateException)、取消通知委托(CancellationToken的Register方法)這些內(nèi)容就留給讀者自己去學(xué)習(xí)了。
有時候,可能需要一次性創(chuàng)建多個任務(wù),并且這些任務(wù)共享相同的狀態(tài)。那么我們就可以通過任務(wù)工廠來創(chuàng)建。
TaskFactory
任務(wù)工廠,顧名思義,用來創(chuàng)建任務(wù)的工廠。在大多數(shù)情況下,不需要實例化一個新?TaskFactory<TResult>?實例。?可以使用靜態(tài)Task<TResult>.Factory?屬性,它返回一個工廠對象,將使用默認(rèn)值。?然后可以調(diào)用其方法來啟動新任務(wù)或定義任務(wù)延續(xù)。
代碼
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());} }注意:任務(wù)可以分配在不同的線程池本地隊列中,由不同的線程調(diào)用并且任務(wù)間并不是串行執(zhí)行,而是并行執(zhí)行。
Parallel
?并行,讓多個線程池線程并行工作。由于是并行執(zhí)行,所以有一點需要注意:工作項彼此之間必須可以并行執(zhí)行!
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,所以當(dāng)For與Foreach都可以時,推薦使用For。
2.上面的代碼,運行For時,你可能會發(fā)現(xiàn)數(shù)字是有順序的打印出來,給人一種串行執(zhí)行的錯覺,你可以斷點調(diào)試你的代碼,會發(fā)現(xiàn)確實有多個線程在運行代碼。
3.Parallel.For()、Parallel.Foreach()還有一些重載方法,大家可以結(jié)合實際情況使用,這里就不復(fù)述了。
感謝大家的耐心閱讀。
?
?
from:https://www.cnblogs.com/color-wolf/p/4850869.html
總結(jié)
以上是生活随笔為你收集整理的线程(Thread,ThreadPool)、Task、Parallel的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 「 深入浅出 」集合List
- 下一篇: Git与GitHub的使用