C#多線程之任務(wù)的用法詳解
Parallel類(http://chabaoo.cn/article/244267.htm)的并行任務(wù)需要結(jié)束后才能運(yùn)行后面的代碼,如果想不等結(jié)束后在開(kāi)始動(dòng)作,可以使用Task類更好地控制并行動(dòng)作。
任務(wù)表示應(yīng)完成的某個(gè)工作單元。這個(gè)工作單元可以在單獨(dú)的線程中運(yùn)行,也可以以同步方式啟動(dòng)一個(gè)任務(wù),這需要等待主調(diào)線程。使用任務(wù)不僅可以獲得一個(gè)抽象層,還可以對(duì)底層線程進(jìn)行很多控制。
任務(wù)相對(duì)Parallel類提供了非常大的靈活性。例如,可以定義連續(xù)的工作——在一個(gè)任務(wù)完成后該執(zhí)行什么工作。這可以根據(jù)任務(wù)成功與否來(lái)分。還可以在層次結(jié)構(gòu)中安排任務(wù)。例如,父任務(wù)可以創(chuàng)建新的子任務(wù)。
一.啟動(dòng)任務(wù)
要啟動(dòng)任務(wù),可以使用TaskFactory類或Task類的構(gòu)造函數(shù)和Start()方法。Task類的構(gòu)造函數(shù)在創(chuàng)建任務(wù)上靈活性比較大。
在啟動(dòng)任務(wù)時(shí),會(huì)創(chuàng)建Task類的一個(gè)實(shí)例,利用Action或Action<T>委托(不帶參數(shù)或帶一個(gè)參數(shù)),可以指定應(yīng)運(yùn)行的代碼。
1.使用線程池的任務(wù)
線程池提供了一個(gè)后臺(tái)線程的池(后面詳細(xì)介紹了線程池)。線程池獨(dú)自管理線程,根據(jù)需要增加或減少線程池中的線程數(shù)。線程池中的線程用于實(shí)現(xiàn)一些動(dòng)作,之后仍然返回線程池中。
下面介紹創(chuàng)建線程池的任務(wù)的四種方法:
先定義一個(gè)要調(diào)用使用的方法:
//避免寫入控制臺(tái)的操作交叉,這里使用lock關(guān)鍵字同步 static object taskMethodLock = new object(); static void TaskMethod(object title) { lock (taskMethodLock) { Console.WriteLine(title); Console.WriteLine("task id:{0},thread:{1}",Task.CurrentId,Thread.CurrentThread.ManagedThreadId); Console.WriteLine("is pooled thread:{0}",Thread.CurrentThread.IsThreadPoolThread); Console.WriteLine("is background thread:{0}",Thread.CurrentThread.IsBackground); } }
(1).使用實(shí)例化的TaskFactory類,把TaskMethod方法和TaskMethod方法的參數(shù)傳遞給StartNew方法:
var tf = new TaskFactory(); Task t1 = tf.StartNew(TaskMethod,"using a task factory");
(2).使用Task類的靜態(tài)屬性Factory來(lái)訪問(wèn)TaskFactory,以調(diào)用StartNew()方法。類似第一種,也使用了工廠,但對(duì)工廠的控制沒(méi)那么全面。
Task t2 = Task.Factory.StartNew(TaskMethod,"using factory via a task");
(3).使用Task的構(gòu)造函數(shù)。實(shí)例化Task對(duì)象時(shí)任務(wù)不會(huì)執(zhí)行,只是指定Created狀態(tài)。接著調(diào)用Start()方法,啟動(dòng)任務(wù)。
Task t3 = new Task(TaskMethod,"using a task constructor and Start"); t3.Start();
(4).直接調(diào)用Task類的Run()方法啟動(dòng)任務(wù)。Run()方法沒(méi)有傳遞帶參數(shù)委托的版本,可以通過(guò)傳遞lambda表達(dá)式。
Task t4 = Task.Run(()=> TaskMethod("using Run method"));
static void Main(string[] args) { var tf = new TaskFactory(); Task t1 = tf.StartNew(TaskMethod,"using a task factory"); Task t2 = Task.Factory.StartNew(TaskMethod,"using factory via a task"); Task t3 = new Task(TaskMethod,"using a task constructor and Start"); t3.Start(); Task t4 = Task.Run(()=> TaskMethod("using Run method")); Console.ReadKey(); }
2.同步任務(wù)
任務(wù)不一定使用線程池中的線程,也可以使用其它線程。任務(wù)也可以同步運(yùn)行,以相同的線程作為主調(diào)線程。
示例:
static void RunSyncTask() { TaskMethod("main thread"); var t = new Task(TaskMethod,"run sync"); t.RunSynchronously(); }
輸出:
上面代碼先在主線程上直接調(diào)用TaskMethod方法,然后在創(chuàng)建的Task上調(diào)用。從輸出看到,主線程是一個(gè)前臺(tái)線程,沒(méi)有任務(wù)ID,也不是線程池中的線程。調(diào)用RunSynchronously方法時(shí),會(huì)使用相同的線程,會(huì)創(chuàng)建一個(gè)任務(wù)。
3.使用單獨(dú)線程的任務(wù)
上面將到的任務(wù)雖然不是線程池中的線程,但使用的是主線程,不是單獨(dú)的,不能實(shí)現(xiàn)異步。
如果任務(wù)的代碼應(yīng)該長(zhǎng)時(shí)間運(yùn)行,就應(yīng)該使用TaskCreationOptions.LongRunning告訴任務(wù)調(diào)度器創(chuàng)建一個(gè)新的單獨(dú)線程,而不是線程池中的線程。這個(gè)線程可以不由線程池管理。當(dāng)線程來(lái)自線程池時(shí),任務(wù)調(diào)度器可以決定等待已經(jīng)運(yùn)行的任務(wù)完成,然后使用這個(gè)線程,而不是在線程池中創(chuàng)建一個(gè)新線程。對(duì)于長(zhǎng)時(shí)間運(yùn)行的線程,任務(wù)調(diào)度器會(huì)立即知道等待它們完成是不明智的做法,會(huì)創(chuàng)建一個(gè)新的線程。
示例:
static void LongRunTask() { var t = new Task(TaskMethod,"long running",TaskCreationOptions.LongRunning); t.Start(); }
輸出:
二.任務(wù)的結(jié)果————Future
任務(wù)結(jié)束時(shí),可以把一些有用的狀態(tài)信息寫入共享對(duì)象中。這個(gè)共享對(duì)象必須是線程安全的。另一個(gè)選項(xiàng)是使用返回某個(gè)結(jié)果的任務(wù)。這種任務(wù)也叫future,因?yàn)樗趯?lái)返回一個(gè)結(jié)果。這需要使用Task類的一個(gè)泛型版本。使用這個(gè)類可以定義任務(wù)返回的結(jié)果的類型。
示例:
使用泛型類Task<TResult>,TResult是返回類型。通過(guò)構(gòu)造函數(shù),把方法傳遞給Func委托,第二個(gè)參數(shù)是委托的參數(shù)。
static void Main(string[] args) { var t = new Task<Tuple<int, int>>(TaskWithResult,Tuple.Create<int,int>(8,3)); t.Start(); Console.WriteLine(t.Result); t.Wait(); Console.WriteLine("result from task:{0},{1}", t.Result.Item1, t.Result.Item2); Console.ReadKey(); }
由任務(wù)來(lái)調(diào)用來(lái)返回結(jié)果的方法可以聲明為任何類型。
static Tuple<int, int> TaskWithResult(object o) { Tuple<int, int> div = (Tuple<int, int>)o; int result = div.Item1 / div.Item2; int reminder = div.Item1 % div.Item2; Thread.Sleep(10000); return Tuple.Create<int, int>(result,reminder); }
這里使用了元組(http://chabaoo.cn/article/244045.htm).
三.連續(xù)的任務(wù)
通過(guò)任務(wù),可以指定在任務(wù)完成后,應(yīng)接著運(yùn)行另一個(gè)特定任務(wù)。例如,一個(gè)使用前一個(gè)任務(wù)的結(jié)果的新任務(wù),如果前一個(gè)任務(wù)失敗了,這個(gè)任務(wù)就應(yīng)執(zhí)行一些清理工作。
任務(wù)處理程序(前一個(gè)任務(wù))或者不帶參數(shù),或者帶一個(gè)對(duì)象參數(shù),而連續(xù)處理程序有一個(gè)Task類型的參數(shù),這里可以訪問(wèn)前一個(gè)任務(wù)的相關(guān)信息。
示例:
//一個(gè)任務(wù)結(jié)束時(shí),可以啟動(dòng)多個(gè)任務(wù),連續(xù)任務(wù)也可以有另一個(gè)連續(xù)的任務(wù)。 static void Main(string[] args) { Task t1 = new Task(DoFirst); Task t2 = t1.ContinueWith(DoSecond); Task t3 = t1.ContinueWith(DoSecond); Task t4= t2.ContinueWith(DoSecond); t1.Start(); Console.ReadKey(); } static void DoFirst() { Console.WriteLine("do some task:{0}",Task.CurrentId); Thread.Sleep(3000); } static void DoSecond(Task t) { Console.WriteLine("task {0} finished",t.Id); Console.WriteLine("this task id:{0}",Task.CurrentId); }
無(wú)論前一個(gè)任務(wù)是如何結(jié)束,前面的連續(xù)任務(wù)總是在前一個(gè)任務(wù)結(jié)束時(shí)啟動(dòng)。使用TaskContinuationOptions枚舉中的值,可以指定,連續(xù)任務(wù)只有在任務(wù)成功或失敗時(shí)啟動(dòng)。
Task t5 = t1.ContinueWith(DoSecond,TaskContinuationOptions.OnlyOnFaulted);
四.任務(wù)的層次結(jié)構(gòu)
利用任務(wù)連續(xù)性,可以在一個(gè)任務(wù)結(jié)束后啟動(dòng)另一個(gè)任務(wù)。任務(wù)也可以構(gòu)成一個(gè)層次結(jié)構(gòu)。在一個(gè)任務(wù)中啟動(dòng)一個(gè)新的任務(wù)時(shí),就啟動(dòng)了一個(gè)父/子層次結(jié)構(gòu)。取消父任務(wù),也會(huì)取消子任務(wù)。
創(chuàng)建子任務(wù)與創(chuàng)建父任務(wù)的代碼相同,唯一區(qū)別就就是子任務(wù)從另一個(gè)任務(wù)內(nèi)部創(chuàng)建。
示例:
static void Main(string[] args) { Task t = new Task(ParentTask); t.Start(); Console.ReadKey(); } static void ParentTask() { Console.WriteLine("parent task id:{0}",Task.CurrentId); var child = new Task(ChildTask); child.Start(); Console.WriteLine("parent create child"); } static void ChildTask() { Console.WriteLine("child task"); }
如果父任務(wù)在子任務(wù)之前結(jié)束,父任務(wù)的狀態(tài)就是WaitingForChildrenToComplete。所有子任務(wù)也結(jié)束時(shí),父任務(wù)的狀態(tài)就是RanToCompletion.
到此這篇關(guān)于C#多線程之任務(wù)的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#基礎(chǔ)之?dāng)?shù)組排序、對(duì)象大小比較實(shí)現(xiàn)代碼
C#基礎(chǔ)之?dāng)?shù)組排序、對(duì)象大小比較實(shí)現(xiàn)代碼,學(xué)習(xí)c#的朋友可以參考下。2011-08-08C#實(shí)現(xiàn)身份證驗(yàn)證功能的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用C#實(shí)現(xiàn)身份證驗(yàn)證功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12利用thrift實(shí)現(xiàn)js與C#通訊的實(shí)例代碼
利用thrift實(shí)現(xiàn)js與C#通訊的實(shí)例代碼,需要的朋友可以參考一下2013-04-04C# 獲取硬盤號(hào),CPU信息,加密解密技術(shù)的步驟
這篇文章主要介紹了C# 獲取硬盤號(hào),CPU信息,加密解密技術(shù)的步驟,幫助大家更好的理解和學(xué)習(xí)c#,感興趣的朋友可以了解下2021-01-01C#實(shí)現(xiàn)簡(jiǎn)易計(jì)算器功能(2)(窗體應(yīng)用)
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)簡(jiǎn)易計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01結(jié)合.net框架在C#派生類中觸發(fā)基類事件及實(shí)現(xiàn)接口事件
這篇文章主要介紹了結(jié)合.net框架在C#派生類中觸發(fā)基類事件及實(shí)現(xiàn)接口事件,示例的事件編程中包括接口和類的繼承等面向?qū)ο蟮幕A(chǔ)知識(shí),需要的朋友可以參考下2016-02-02C#隨機(jī)設(shè)置900-1100毫秒延遲的方法
這篇文章主要介紹了C#隨機(jī)設(shè)置900-1100毫秒延遲的方法,涉及C#中Thread.Sleep方法的使用技巧,需要的朋友可以參考下2015-04-04WPF模擬實(shí)現(xiàn)Gitee泡泡菜單的示例代碼
這篇文章主要介紹了如何利用WPF模擬實(shí)現(xiàn)Gitee泡泡菜單,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定幫助,需要的可以參考一下2022-08-08