[深入學習C#]C#實現多線程的方式:Task——任務
簡介
.NET 4包含新名稱空間System.Threading.Tasks,它 包含的類抽象出了線程功能。 在後臺使用ThreadPool。 任務表示應完成的某個單元的工作。 這個單元的工作可以在單獨的線程中運行,也可以以同步方式啟動一個任務,這需要等待主調線程。 使用任務不僅可以獲得一個抽象層,還可以對底層線程進行很多控制。
在安排需要完成的工作時,任務提供了非常大的靈活性。 例如,可 以定義連續的工 作—— 在一個任務完成後該執行什麽工作。 這可以區分任務成功與否。 另外,還可以在層次結構中安排任務。例如,父任務可以創建新的子任務。 這可以創建一種依賴關系,這樣,取消父任務,也會取消其子任務。
啟動任務
要啟動任務,可 以使用 TaskFactory類 或 Task類 的構造函數和 Start()方法。Task類的構造函數在創建任務上提供的靈活性較大。
在啟動任務時,會創建Task類 的一個實例,利用Action或Action<
object>
委托不帶參數或帶一個object參數 ,可以指定應運行的代碼,這類似於Thread類 。下面定義了一個無參數的方法。 在實現代碼中,把任務的ID寫入控制臺中:
static void TaskMethod()
{
Console.WriteLine("running in a task");
Console.WriteLine("Task id: {0}",Task.CurrentId);
}
在上面的代碼中,可 以看到啟動新任務的不同方式。第一種方式 使用實例化TaskFactory類 ,在其中把 TaskMedlod()方 法傳遞給StartNew()方法,就會立即啟動任務。 第二種方式使用 Task類的構造函數。 實例化 Task對象時,任務不會立即運行,而是指定 Created狀態。接著調用 Task類的Start()方法,來啟動任務。 使用Task類 時,除了調用 Start()方法,還可以調用RunSynchronously()方法。這樣,任務也會啟動,但在調用者的當前線程中它正在運行,調用者需要一直等待到該任務結束。 默認情況下,任務是異步運行的。
//using task factory
TaskFactory tf = new TaskFactory();
Task t1 = tf.StartNew(TaskMethod);
//using the task factory via a task
Task t2 = Task.TaskFactory.StartNew(TaskMethod);
//using Task constructor
Task t3 = new Task(TaskMethod);
t3.Start();
使用Task類的構造函數和TaskFactory類的StartNew()方法時,都可以傳遞TaskCreationOptions枚舉中的值。設置LongRunning選項,可以通知任務調度器,該任務需要較長時間執行,這樣調度器更可能使用新線程。如果該任務應關聯到父任務上,而父任務取消了,則該任務也應取消,此時應設置 AuachToParent選項。PreferFairness的值表示,調度器應提取出已在等待的第一個任務。 如果一個任務在另一個任務內部創建,這就不是默認情況 。如果任務使用子任務創建了其他工作,子任務就優先於其他任務。 它們不會排在線程池隊列中的最後。 如果這些任務應以公平的方式與所有其他任務一起處理,就設置該選項為PreferFairness。
Task t4 = new Task(TaskMethod, TaskCreationOptions.PreferFairness);
t4.Start();
連續任務
通過任務,可 以指定在任務完成後,應開始運行另一個特定任務,例如,一個使用前一個任務的結果的新任務,如 果前一個任務失敗了,這個任務就應執行一些清理工作。
任務處理程序或者不帶參數或者帶一個對象參數,而連續處理程序有一個 Task類 型的參數,這裏可以訪問起始任務的相關信息:
static void DoOnFirst()
{
Console.WriteLine("doing some task {0}",Task.CurrentId);
Thread.Sleep(3000);
}
static void DoOnSecond(Task t)
{
Console.WriteLine("task {0} finished", t.Id);
Console.WriteLine("this task id {0}", Task.CurrentId);
Console.WriteLine("do some cleanup");
Thread.Sleep(3000);
}
連續任務通過在任務上調用ContinueWith()方法來定義。也可以使用TaskFactory類來定義。t1.ContinueWith(DoOnSecond)方 法表示,調用DoOnSecond()方法的新任務應在任務t1結束時立即啟動。在一個任務結束時,可以啟動多個任務,連續任務也可以有另一個連續任務,如下面的例子所示:
Task t1 = new Task(DoOnFirst);
Task t2 = t1.ContinueWith(DoOnSecond);
Task t3 = t1.ContinueWith(DoOnSecond);
Task t4 = t2.ContinueWith(DoOnSecond);
無論前一個任務是如何結束的,前 面的連續任務總是在前一個任務結束時啟動。 使用TaskContinuationOptions枚舉中的值,可以指定,連續任務只有在起始任務(或失敗)結束時啟動。一些可能的值是OnlyOnFaulted、 NotOnFaulted、 OnlyOnCanceled、 NotOnCanceled和OnlyOnRanToCompletion。
Task t5 = t1.ContinueWith(DoOnError, TaskContinuationOptions.OnlyOnFaulted);
任務層次結構
利用任務連續性,可 以在一個任務結束後啟動另一個任務。 任務也可以構成一個層次結構。 一個任務啟動一個新任務時,就啟動了一個父/子層次結構。
下面的代碼段在父任務內部新建一個任務。 創建子任務的代碼與創建父任務的代碼相同,唯一的區別是這個任務從另一個任務內部創建。
static void ParentAndChild()
{
var parent = new Task(ParentTask);
parent.Start();
Thread.Sleep(2000);
Console.WriteLine(parent.Status);
Thread.Sleep(4000);
Console.WriteLine(parent.Status);
}
static void ParentTask()
{
Console.WriteLine("task id {0}", Task.CurrentId);
var child = new Task(ChildTask);
child.Start();
Thread.Sleep(1000);
Console.WriteLine("parent started child");
}
static void ChildTask()
{
Console.WriteLine("child");
Thread.Sleep(5000);
Console.WriteLine("child finished");
}
如果父任務在子任務之前結束,父任務的狀態就顯示為WaitingForChildrenToComplete。 只要子任務也結束時,父任務的狀態就變成RanToCompletion。 當然,如 果父任務用TaskCreationOptions枚舉中的 DetachedFromParent創建子任務時,這就無效。
任務的結果
任務結束時,它可以把一些有用的狀態信息寫到共享對象中。這個共享對象必須是線程安全的。另一個選項是使用返回某個結果的任務。使用 Task類 的泛型版本,就可以定義返回某個結果的任務的返回類型。
為了返回某個結果任務調用的方法可以聲明為帶任意返回類型。示例方法TaskWithResult()利用一個元組返回兩個int值。 該方法的輸入可以是void或object類型,如下所示:
static Tuple<int, int> TaskWithResult(object division)
{
Tuple<int, int> div =(Tuple<int, int>)division;
int result = div.Item1/div.Item2;
int reminder = div.Item1%div.Item2;
Console.WriteLine("task creates a result...");
return Tuple.Create<int, int>(result, reminder);
}
定義一個調用 StartWithResult()方法的任務時,要使用泛型類Task<
Tresult>
。 泛型參數定義了返回類型。通過構造函數,把這個方法傳遞給Func委 托,第二個參數定義了輸入值。 因為這個任務在object參數中需要兩個輸入值,所以還創建了一個元組。 接著啟動該任務。 Task實例t1的Result屬性被禁用,並一直等到該任務完成。任務完成後,Result屬性包含任務的結果。
var t1 = new Task<Tuple<int, int>>(TaskWithResult, Tuple.Create<int, int>(8, 3));
t1.Start();
Console.WriteLine(t1.Result);
t1.Wait();
Console.WriteLine("result from task: {0} {1}",t1.Result.Item1, t1.Result.Item2);
備註:上例中,Task<
Tresult>
構造函數調用了TaskFactory.StartNew 方法的 (Func<
Object, TResult>
, Object)重載。
function
類型:System.Func<
Object, TResult>
一個函數委托,可返回能夠通過任務獲得的將來結果。
state
類型:System.Object
包含 function 委托所用數據的對象。
因此我們可以知道,為什麽在實例化t1的時候,為什麽要創建一個新的Tuple對象了。
[深入學習C#]C#實現多線程的方式:Task——任務