安慶網(wǎng)站建設(shè)服務(wù)網(wǎng)蘇州關(guān)鍵詞搜索排名
本文內(nèi)容
- 隱式創(chuàng)建和運行任務(wù)
- 顯式創(chuàng)建和運行任務(wù)
- 任務(wù) ID
- 任務(wù)創(chuàng)建選項
- 任務(wù)、線程和區(qū)域性
- 創(chuàng)建任務(wù)延續(xù)
- 創(chuàng)建分離的子任務(wù)
- 創(chuàng)建子任務(wù)
- 等待任務(wù)完成
- 組合任務(wù)
- 處理任務(wù)中的異常
- 取消任務(wù)
- TaskFactory 類
- 無委托的任務(wù)
- 自定義計劃程序
- 相關(guān)數(shù)據(jù)結(jié)構(gòu)
- 自定義任務(wù)類型
任務(wù)并行庫 (TPL) 以“任務(wù)”的概念為基礎(chǔ),后者表示異步操作。 在某些方面,任務(wù)類似于線程或?ThreadPool?工作項,但是抽象級別更高。 術(shù)語“任務(wù)并行”是指一個或多個獨立的任務(wù)同時運行。 任務(wù)提供兩個主要好處:
-
系統(tǒng)資源的使用效率更高,可伸縮性更好。
在后臺,任務(wù)排隊到已使用算法增強的?ThreadPool,這些算法能夠確定線程數(shù)并隨之調(diào)整。 這些算法提供負載平衡以實現(xiàn)吞吐量最大化。 此進程會使任務(wù)相對輕量,你可以創(chuàng)建很多任務(wù)以啟用細化并行。
-
對于線程或工作項,可以使用更多的編程控件。
任務(wù)和圍繞它們生成的框架提供了一組豐富的 API,這些 API 支持等待、取消、繼續(xù)、可靠的異常處理、詳細狀態(tài)、自定義計劃等功能。
出于這兩個原因,在 .NET 中,TPL 是用于編寫多線程、異步和并行代碼的首選 API。
1、隱式創(chuàng)建和運行任務(wù)
Parallel.Invoke?方法提供了一種簡便方式,可同時運行任意數(shù)量的任意語句。 只需為每個工作項傳入?Action?委托即可。 創(chuàng)建這些委托的最簡單方式是使用 lambda 表達式。 lambda 表達式可調(diào)用指定的方法,或提供內(nèi)聯(lián)代碼。 下面的示例演示一個基本的?Invoke?調(diào)用,該調(diào)用創(chuàng)建并啟動同時運行的兩個任務(wù)。 第一個任務(wù)由調(diào)用名為?DoSomeWork
?的方法的 lambda 表達式表示,第二個任務(wù)由調(diào)用名為?DoSomeOtherWork
?的方法的 lambda 表達式表示。
?備注
本文檔使用 lambda 表達式在 TPL 中定義委托。?
Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
?備注
Invoke?在后臺創(chuàng)建的?Task?實例數(shù)不一定與所提供的委托數(shù)相等。 TPL 可能會使用各種優(yōu)化,特別是對于大量的委托。
為了更好地控制任務(wù)執(zhí)行或從任務(wù)返回值,必須更加顯式地使用?Task?對象。
2、顯式創(chuàng)建和運行任務(wù)
不返回值的任務(wù)由?System.Threading.Tasks.Task?類表示。 返回值的任務(wù)由?System.Threading.Tasks.Task<TResult>?類表示,該類從?Task?繼承。 任務(wù)對象處理基礎(chǔ)結(jié)構(gòu)詳細信息,并提供可在任務(wù)的整個生存期內(nèi)從調(diào)用線程訪問的方法和屬性。 例如,可以隨時訪問任務(wù)的?Status?屬性,以確定它是已開始運行、已完成運行、已取消還是引發(fā)了異常。 狀態(tài)由?TaskStatus?枚舉表示。
在創(chuàng)建任務(wù)時,你賦予它一個用戶委托,該委托封裝該任務(wù)將執(zhí)行的代碼。 該委托可以表示為命名的委托、匿名方法或 lambda 表達式。 lambda 表達式可以包含對命名方法的調(diào)用,如下面的示例所示。 該示例包含對?Task.Wait?方法的調(diào)用,以確保任務(wù)在控制臺模式應(yīng)用程序結(jié)束之前完成執(zhí)行。
using System;
using System.Threading;
using System.Threading.Tasks;public class Lambda
{public static void Main(){Thread.CurrentThread.Name = "Main";// Create a task and supply a user delegate by using a lambda expression.Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));// Start the task.taskA.Start();// Output a message from the calling thread.Console.WriteLine("Hello from thread '{0}'.",Thread.CurrentThread.Name);taskA.Wait();}
}
// The example displays output as follows:
// Hello from thread 'Main'.
// Hello from taskA.
// or
// Hello from taskA.
// Hello from thread 'Main'.
你還可以使用?Task.Run?方法通過一個操作創(chuàng)建并啟動任務(wù)。 無論是哪個任務(wù)計劃程序與當(dāng)前線程關(guān)聯(lián),Run?方法都將使用默認的任務(wù)計劃程序來管理任務(wù)。 不需要對任務(wù)的創(chuàng)建和計劃進行更多控制時,首選?Run?方法創(chuàng)建并啟動任務(wù)。
using System;
using System.Threading;
using System.Threading.Tasks;namespace Run;public class Example
{public static void Main(){Thread.CurrentThread.Name = "Main";// Define and run the task.Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));// Output a message from the calling thread.Console.WriteLine("Hello from thread '{0}'.",Thread.CurrentThread.Name);taskA.Wait();}
}
// The example displays output as follows:
// Hello from thread 'Main'.
// Hello from taskA.
// or
// Hello from taskA.
// Hello from thread 'Main'.
你還可以使用?TaskFactory.StartNew?方法在一個操作中創(chuàng)建并啟動任務(wù)。 如下例所示,可以在以下情況下使用此方法:
-
無需將創(chuàng)建與計劃分開,且需要額外的任務(wù)創(chuàng)建選項或需要使用特定的計劃程序。
-
需要將其他狀態(tài)傳遞給可通過其?Task.AsyncState?屬性檢索的任務(wù)。
using System;
using System.Threading;
using System.Threading.Tasks;namespace TaskIntro;class CustomData
{public long CreationTime;public int Name;public int ThreadNum;
}public class AsyncState
{public static void Main(){Task[] taskArray = new Task[10];for (int i = 0; i < taskArray.Length; i++){taskArray[i] = Task.Factory.StartNew((Object obj) =>{CustomData data = obj as CustomData;if (data == null) return;data.ThreadNum = Thread.CurrentThread.ManagedThreadId;},new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });}Task.WaitAll(taskArray);foreach (var task in taskArray){var data = task.AsyncState as CustomData;if (data != null)Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",data.Name, data.CreationTime, data.ThreadNum);}}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583, ran on thread #3.
// Task #1 created at 635116412924607584, ran on thread #4.
// Task #2 created at 635116412924607584, ran on thread #4.
// Task #3 created at 635116412924607584, ran on thread #4.
// Task #4 created at 635116412924607584, ran on thread #3.
// Task #5 created at 635116412924607584, ran on thread #3.
// Task #6 created at 635116412924607584, ran on thread #4.
// Task #7 created at 635116412924607584, ran on thread #4.
// Task #8 created at 635116412924607584, ran on thread #3.
// Task #9 created at 635116412924607584, ran on thread #4.
Task?和?Task<TResult>?均公開靜態(tài)?Factory?屬性,該屬性返回?TaskFactory?的默認實例,因此你可以調(diào)用該方法為?Task.Factory.StartNew()
。 此外,在以下示例中,由于任務(wù)的類型為?System.Threading.Tasks.Task<TResult>,因此每個任務(wù)都具有包含計算結(jié)果的公共?Task<TResult>.Result?屬性。 任務(wù)以異步方式運行,可以按任意順序完成。 如果在計算完成之前訪問?Result?屬性,則該屬性將阻止調(diào)用線程。
使用 lambda 表達式創(chuàng)建委托時,你有權(quán)訪問源代碼中當(dāng)時可見的所有變量。 然而,在某些情況下,特別是在循環(huán)中,lambda 不按照預(yù)期的方式捕獲變量。 它僅捕獲變量的引用,而不是它每次迭代后更改的值。 以下示例演示了該問題。 它將循環(huán)計數(shù)器傳遞給實例化?CustomData
?對象并使用循環(huán)計數(shù)器作為對象標識符的 lambda 表達式。 如示例輸出所示,每個?CustomData
?對象都具有相同的標識符。
using System;
using System.Threading;
using System.Threading.Tasks;namespace Example.Iterations;class CustomData
{public long CreationTime;public int Name;public int ThreadNum;
}public class IterationTwo
{public static void Main(){// Create the task object by using an Action(Of Object) to pass in the loop// counter. This produces an unexpected result.Task[] taskArray = new Task[10];for (int i = 0; i < taskArray.Length; i++) {taskArray[i] = Task.Factory.StartNew( (Object obj) => {var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};data.ThreadNum = Thread.CurrentThread.ManagedThreadId;Console.WriteLine("Task #{0} created at {1} on thread #{2}.",data.Name, data.CreationTime, data.ThreadNum);},i );}Task.WaitAll(taskArray);}
}
// The example displays output like the following:
// Task #10 created at 635116418427727841 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427727841 on thread #3.
// Task #10 created at 635116418427747843 on thread #3.
// Task #10 created at 635116418427747843 on thread #3.
// Task #10 created at 635116418427737842 on thread #4.
通過使用構(gòu)造函數(shù)向任務(wù)提供狀態(tài)對象,可以在每次迭代時訪問該值。 以下示例在上一示例的基礎(chǔ)上做了修改,在創(chuàng)建?CustomData
?對象時使用循環(huán)計數(shù)器,該對象繼而傳遞給 lambda 表達式。 如示例輸出所示,每個?CustomData
?對象現(xiàn)在都具有唯一的一個標識符,該標識符基于該對象實例化時循環(huán)計數(shù)器的值。
using System;
using System.Threading;
using System.Threading.Tasks;class CustomData
{public long CreationTime;public int Name;public int ThreadNum;
}public class IterationOne
{public static void Main(){// Create the task object by using an Action(Of Object) to pass in custom data// to the Task constructor. This is useful when you need to capture outer variables// from within a loop.Task[] taskArray = new Task[10];for (int i = 0; i < taskArray.Length; i++) {taskArray[i] = Task.Factory.StartNew( (Object obj ) => {CustomData data = obj as CustomData;if (data == null)return;data.ThreadNum = Thread.CurrentThread.ManagedThreadId;Console.WriteLine("Task #{0} created at {1} on thread #{2}.",data.Name, data.CreationTime, data.ThreadNum);},new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );}Task.WaitAll(taskArray);}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583 on thread #3.
// Task #1 created at 635116412924607584 on thread #4.
// Task #3 created at 635116412924607584 on thread #4.
// Task #4 created at 635116412924607584 on thread #4.
// Task #2 created at 635116412924607584 on thread #3.
// Task #6 created at 635116412924607584 on thread #3.
// Task #5 created at 635116412924607584 on thread #4.
// Task #8 created at 635116412924607584 on thread #4.
// Task #7 created at 635116412924607584 on thread #3.
// Task #9 created at 635116412924607584 on thread #4.
此狀態(tài)作為參數(shù)傳遞給任務(wù)委托,并且可通過使用?Task.AsyncState?屬性從任務(wù)對象訪問。 以下示例在上一示例的基礎(chǔ)上演變而來。 它使用?AsyncState?屬性顯示關(guān)于傳遞到 lambda 表達式的?CustomData
?對象的信息。
using System;
using System.Threading;
using System.Threading.Tasks;namespace TaskIntro;class CustomData
{public long CreationTime;public int Name;public int ThreadNum;
}public class AsyncState
{public static void Main(){Task[] taskArray = new Task[10];for (int i = 0; i < taskArray.Length; i++){taskArray[i] = Task.Factory.StartNew((Object obj) =>{CustomData data = obj as CustomData;if (data == null) return;data.ThreadNum = Thread.CurrentThread.ManagedThreadId;},new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });}Task.WaitAll(taskArray);foreach (var task in taskArray){var data = task.AsyncState as CustomData;if (data != null)Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",data.Name, data.CreationTime, data.ThreadNum);}}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583, ran on thread #3.
// Task #1 created at 635116412924607584, ran on thread #4.
// Task #2 created at 635116412924607584, ran on thread #4.
// Task #3 created at 635116412924607584, ran on thread #4.
// Task #4 created at 635116412924607584, ran on thread #3.
// Task #5 created at 635116412924607584, ran on thread #3.
// Task #6 created at 635116412924607584, ran on thread #4.
// Task #7 created at 635116412924607584, ran on thread #4.
// Task #8 created at 635116412924607584, ran on thread #3.
// Task #9 created at 635116412924607584, ran on thread #4.
3、任務(wù) ID
每個任務(wù)都獲得一個在應(yīng)用程序域中唯一標識自己的整數(shù) ID,可以使用?Task.Id?屬性訪問該 ID。 該 ID 可有效用于在 Visual Studio 調(diào)試器的“并行堆?!焙汀叭蝿?wù)”窗口中查看任務(wù)信息。 該 ID 是以惰性方式創(chuàng)建的,這意味著請求該 ID 時才會創(chuàng)建該 ID。 因此,每次運行程序時,任務(wù)可能具有不同的 ID。?
4、任務(wù)創(chuàng)建選項
創(chuàng)建任務(wù)的大多數(shù) API 提供接受?TaskCreationOptions?參數(shù)的重載。 通過指定下列某個或多個選項,可指示任務(wù)計劃程序在線程池中安排任務(wù)計劃的方式。 可以使用位 OR 運算組合選項。
下面的示例演示一個具有?LongRunning?和?PreferFairness?選項的任務(wù):
var task3 = new Task(() => MyLongRunningMethod(),TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();
5、任務(wù)、線程和區(qū)域性
每個線程都具有一個關(guān)聯(lián)的區(qū)域性和 UI 區(qū)域性,分別由?Thread.CurrentCulture?和?Thread.CurrentUICulture?屬性定義。 線程的區(qū)域性用在格式設(shè)置、分析、排序和字符串比較等操作中。 線程的 UI 區(qū)域性用于查找資源。
系統(tǒng)區(qū)域性定義線程的默認區(qū)域性和 UI 區(qū)域性。 但你可以使用?CultureInfo.DefaultThreadCurrentCulture?和?CultureInfo.DefaultThreadCurrentUICulture?屬性為應(yīng)用程序域中的所有線程指定默認區(qū)域性。 如果你顯式設(shè)置線程的區(qū)域性并啟動新線程,則新線程不會繼承正在調(diào)用的線程的區(qū)域性;相反,其區(qū)域性就是默認系統(tǒng)區(qū)域性。 但是,在基于任務(wù)的編程中,任務(wù)使用調(diào)用線程的區(qū)域性,即使任務(wù)在不同線程上以異步方式運行也是如此。
下面的示例提供了簡單的演示。 它將應(yīng)用的當(dāng)前區(qū)域性更改為法語(法國)。 如果法語(法國)已經(jīng)是當(dāng)前區(qū)域性,它會更改為英語(美國)。 然后,調(diào)用一個名為?formatDelegate
?的委托,該委托返回在新區(qū)域性中格式化為貨幣值的數(shù)字。 無論委托是由任務(wù)同步調(diào)用還是異步調(diào)用,該任務(wù)都將使用調(diào)用線程的區(qū)域性。
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;public class Example
{public static void Main(){decimal[] values = { 163025412.32m, 18905365.59m };string formatString = "C2";Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",CultureInfo.CurrentCulture.Name,Thread.CurrentThread.ManagedThreadId);foreach (var value in values)output += String.Format("{0} ", value.ToString(formatString));output += Environment.NewLine;return output;};Console.WriteLine("The example is running on thread {0}",Thread.CurrentThread.ManagedThreadId);// Make the current culture different from the system culture.Console.WriteLine("The current culture is {0}",CultureInfo.CurrentCulture.Name);if (CultureInfo.CurrentCulture.Name == "fr-FR")Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");elseThread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");Console.WriteLine("Changed the current culture to {0}.\n",CultureInfo.CurrentCulture.Name);// Execute the delegate synchronously.Console.WriteLine("Executing the delegate synchronously:");Console.WriteLine(formatDelegate());// Call an async delegate to format the values using one format string.Console.WriteLine("Executing a task asynchronously:");var t1 = Task.Run(formatDelegate);Console.WriteLine(t1.Result);Console.WriteLine("Executing a task synchronously:");var t2 = new Task<String>(formatDelegate);t2.RunSynchronously();Console.WriteLine(t2.Result);}
}
// The example displays the following output:
// The example is running on thread 1
// The current culture is en-US
// Changed the current culture to fr-FR.
//
// Executing the delegate synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €
//
// Executing a task asynchronously:
// Formatting using the fr-FR culture on thread 3.
// 163 025 412,32 € 18 905 365,59 €
//
// Executing a task synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €
在 .NET Framework 4.6 之前的 .NET Framework 版本中,任務(wù)的區(qū)域性由它在其上運行的線程區(qū)域性確定,而不是調(diào)用線程的區(qū)域性。 對于異步任務(wù),任務(wù)使用的區(qū)域性可能不同于調(diào)用線程的區(qū)域性。
有關(guān)異步任務(wù)和區(qū)域性的詳細信息,請參閱?CultureInfo?一文中的“區(qū)域性和基于異步任務(wù)的操作”部分。
6、創(chuàng)建任務(wù)延續(xù)
使用?Task.ContinueWith?和?Task<TResult>.ContinueWith?方法,可以指定要在先行任務(wù)完成時啟動的任務(wù)。 延續(xù)任務(wù)的委托被傳遞給對先行任務(wù)的引用,以便它查看先行任務(wù)的狀態(tài)。 通過檢索?Task<TResult>.Result?屬性的值,可以將先行任務(wù)的輸出用作延續(xù)任務(wù)的輸入。
在下面的示例中,getData
?任務(wù)通過調(diào)用?TaskFactory.StartNew<TResult>(Func<TResult>)?方法來啟動。 當(dāng)?processData
?完成時,getData
?任務(wù)自動啟動,當(dāng)?displayData
?完成時,processData
?啟動。?getData
?產(chǎn)生一個整數(shù)數(shù)組,通過?processData
?任務(wù)的?getData
?屬性,Task<TResult>.Result?任務(wù)可訪問該數(shù)組。?processData
?任務(wù)處理該數(shù)組并返回結(jié)果,結(jié)果的類型從傳遞到?Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>)?方法的 Lambda 表達式的返回類型推斷而來。?displayData
?完成時,processData
?任務(wù)自動執(zhí)行,而?Tuple<T1,T2,T3>?任務(wù)可通過?processData
?任務(wù)的?displayData
?屬性訪問由?processData
?lambda 表達式返回的?Task<TResult>.Result?對象。?displayData
?任務(wù)采用?processData
?任務(wù)的結(jié)果。 它得出自己的結(jié)果,其類型以相似方式推斷而來,且可由程序中的?Result?屬性使用。
using System;
using System.Threading.Tasks;public class ContinuationOne
{public static void Main(){var getData = Task.Factory.StartNew(() => {Random rnd = new Random();int[] values = new int[100];for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)values[ctr] = rnd.Next();return values;} );var processData = getData.ContinueWith((x) => {int n = x.Result.Length;long sum = 0;double mean;for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)sum += x.Result[ctr];mean = sum / (double) n;return Tuple.Create(n, sum, mean);} );var displayData = processData.ContinueWith((x) => {return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",x.Result.Item1, x.Result.Item2,x.Result.Item3);} );Console.WriteLine(displayData.Result);}
}
// The example displays output similar to the following:
// N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
因為?Task.ContinueWith?是實例方法,所以你可以將方法調(diào)用鏈接在一起,而不是為每個先行任務(wù)去實例化?Task<TResult>?對象。 以下示例與上一示例在功能上等同,唯一的不同在于它將對?Task.ContinueWith?方法的調(diào)用鏈接在一起。 通過方法調(diào)用鏈返回的?Task<TResult>?對象是最終延續(xù)任務(wù)。
using System;
using System.Threading.Tasks;public class ContinuationTwo
{public static void Main(){var displayData = Task.Factory.StartNew(() => {Random rnd = new Random();int[] values = new int[100];for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)values[ctr] = rnd.Next();return values;} ).ContinueWith((x) => {int n = x.Result.Length;long sum = 0;double mean;for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)sum += x.Result[ctr];mean = sum / (double) n;return Tuple.Create(n, sum, mean);} ).ContinueWith((x) => {return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",x.Result.Item1, x.Result.Item2,x.Result.Item3);} );Console.WriteLine(displayData.Result);}
}
// The example displays output similar to the following:
// N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
使用?ContinueWhenAll?和?ContinueWhenAny?方法,可以從多個任務(wù)繼續(xù)。
7、創(chuàng)建分離的子任務(wù)
如果在任務(wù)中運行的用戶代碼創(chuàng)建一個新任務(wù),且未指定?AttachedToParent?選項,則該新任務(wù)不采用任何特殊方式與父任務(wù)同步。 這種不同步的任務(wù)類型稱為“分離的嵌套任務(wù)”或“分離的子任務(wù)”。 以下示例展示了創(chuàng)建一個分離子任務(wù)的任務(wù):
var outer = Task.Factory.StartNew(() =>
{Console.WriteLine("Outer task beginning.");var child = Task.Factory.StartNew(() =>{Thread.SpinWait(5000000);Console.WriteLine("Detached task completed.");});
});outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
// Outer task beginning.
// Outer task completed.
// Detached task completed.
?備注
父任務(wù)不會等待分離子任務(wù)完成。
8、創(chuàng)建子任務(wù)
如果任務(wù)中運行的用戶代碼在創(chuàng)建任務(wù)時指定了?AttachedToParent?選項,新任務(wù)就稱為父任務(wù)的附加子任務(wù)。 因為父任務(wù)隱式地等待所有附加子任務(wù)完成,所以你可以使用?AttachedToParent?選項表示結(jié)構(gòu)化的任務(wù)并行。 以下示例展示了創(chuàng)建 10 個附加子任務(wù)的父任務(wù)。 該示例調(diào)用?Task.Wait?方法來等待父任務(wù)完成。 它不必顯式等待附加子任務(wù)完成。
using System;
using System.Threading;
using System.Threading.Tasks;public class Child
{public static void Main(){var parent = Task.Factory.StartNew(() => {Console.WriteLine("Parent task beginning.");for (int ctr = 0; ctr < 10; ctr++) {int taskNo = ctr;Task.Factory.StartNew((x) => {Thread.SpinWait(5000000);Console.WriteLine("Attached child #{0} completed.",x);},taskNo, TaskCreationOptions.AttachedToParent);}});parent.Wait();Console.WriteLine("Parent task completed.");}
}
// The example displays output like the following:
// Parent task beginning.
// Attached child #9 completed.
// Attached child #0 completed.
// Attached child #8 completed.
// Attached child #1 completed.
// Attached child #7 completed.
// Attached child #2 completed.
// Attached child #6 completed.
// Attached child #3 completed.
// Attached child #5 completed.
// Attached child #4 completed.
// Parent task completed.
父任務(wù)可使用?TaskCreationOptions.DenyChildAttach?選項阻止其他任務(wù)附加到父任務(wù)。?
9、等待任務(wù)完成
System.Threading.Tasks.Task?和?System.Threading.Tasks.Task<TResult>?類型提供了?Task.Wait?方法的若干重載,以便能夠等待任務(wù)完成。 此外,使用靜態(tài)?Task.WaitAll?和?Task.WaitAny?方法的重載可以等待一批任務(wù)中的任一任務(wù)或所有任務(wù)完成。
通常,會出于以下某個原因等待任務(wù):
-
主線程依賴于任務(wù)計算的最終結(jié)果。
-
你必須處理可能從任務(wù)引發(fā)的異常。
-
應(yīng)用程序可以在所有任務(wù)執(zhí)行完畢之前終止。 例如,執(zhí)行?
Main
(應(yīng)用程序入口點)中的所有同步代碼后,控制臺應(yīng)用程序?qū)⒔K止。
下面的示例演示不包含異常處理的基本模式:
Task[] tasks = new Task[3]
{Task.Factory.StartNew(() => MethodA()),Task.Factory.StartNew(() => MethodB()),Task.Factory.StartNew(() => MethodC())
};//Block until all tasks complete.
Task.WaitAll(tasks);// Continue on this thread...
某些重載允許你指定超時,而其他重載采用額外的?CancellationToken?作為輸入?yún)?shù),以便可以通過編程方式或根據(jù)用戶輸入來取消等待。
等待任務(wù)時,其實是在隱式等待使用?TaskCreationOptions.AttachedToParent?選項創(chuàng)建的該任務(wù)的所有子級。?Task.Wait?在該任務(wù)已完成時立即返回。?Task.Wait?方法將拋出由某任務(wù)引發(fā)的任何異常,即使?Task.Wait?方法是在該任務(wù)完成之后調(diào)用的。
10、組合任務(wù)
Task?和?Task<TResult>?類提供了多種方法來幫助組合多個任務(wù)。 這些方法實現(xiàn)常用模式并更好地利用 C#、Visual Basic 和 F# 提供的異步語言功能。 本節(jié)介紹了?WhenAll、WhenAny、Delay?和?FromResult?方法。
Task.WhenAll
Task.WhenAll?方法異步等待多個?Task?或?Task<TResult>?對象完成。 通過它提供的重載版本可以等待非均勻任務(wù)組。 例如,你可以等待多個?Task?和?Task<TResult>?對象在一個方法調(diào)用中完成。
Task.WhenAny
Task.WhenAny?方法異步等待多個?Task?或?Task<TResult>?對象中的一個完成。 與在?Task.WhenAll?方法中一樣,該方法提供重載版本,讓你能等待非均勻任務(wù)組。?WhenAny?方法在下列情境中尤其有用:
-
冗余運算:請考慮可以用多種方式執(zhí)行的算法或運算。 你可使用?WhenAny?方法來選擇先完成的運算,然后取消剩余的運算。
-
交錯運算:你可啟動必須完成的多項運算,并使用?WhenAny?方法在每項運算完成時處理結(jié)果。 在一項運算完成后,可以啟動一個或多個任務(wù)。
-
限制運算:你可使用?WhenAny?方法通過限制并發(fā)運算的數(shù)量來擴展前面的情境。
-
到期運算:你可使用?WhenAny?方法在一個或多個任務(wù)與特定時間后完成的任務(wù)(例如?Delay?方法返回的任務(wù))間進行選擇。 下節(jié)描述了?Delay?方法。
Task.Delay
Task.Delay?方法將生成在指定時間后完成的?Task?對象。 你可以使用此方法生成用于輪詢數(shù)據(jù)的循環(huán),指定超時,延遲處理用戶輸入等。
Task(T).FromResult
通過使用?Task.FromResult?方法,你可以創(chuàng)建包含預(yù)計算結(jié)果的?Task<TResult>?對象。 執(zhí)行返回?Task<TResult>?對象的異步運算,且已計算該?Task<TResult>?對象的結(jié)果時,此方法將十分有用。
11、處理任務(wù)中的異常
當(dāng)某個任務(wù)拋出一個或多個異常時,異常包裝在?AggregateException?異常中。 該異常會傳播回與任務(wù)聯(lián)接的線程。 通常,該線程是等待任務(wù)完成的線程或訪問?Result?屬性的線程。 此行為強制實施 .NET Framework 策略 - 默認所有未處理的異常應(yīng)終止進程。 調(diào)用代碼可以通過使用?try
/catch
?塊中的以下任意方法來處理異常:
-
Wait?方法
-
WaitAll?方法
-
WaitAny?方法
-
Result?屬性
聯(lián)接線程也可以通過在對任務(wù)進行垃圾回收之前訪問?Exception?屬性來處理異常。 通過訪問此屬性,可防止未處理的異常在對象完成時觸發(fā)終止進程的異常傳播行為。
12、取消任務(wù)
Task?類支持協(xié)作取消,并與 .NET Framework 4 中新增的?System.Threading.CancellationTokenSource?類和?System.Threading.CancellationToken?類完全集成。?System.Threading.Tasks.Task?類中的大多數(shù)構(gòu)造函數(shù)采用?CancellationToken?對象作為輸入?yún)?shù)。 許多?StartNew?和?Run?重載還包括?CancellationToken?參數(shù)。
你可以創(chuàng)建標記,并使用?CancellationTokenSource?類在以后某一時間發(fā)出取消請求。 可以將該標記作為參數(shù)傳遞給?Task,還可以在執(zhí)行響應(yīng)取消請求的工作的用戶委托中引用同一標記。
13、TaskFactory 類
TaskFactory?類提供靜態(tài)方法,這些方法封裝了用于創(chuàng)建和啟動任務(wù)和延續(xù)任務(wù)的常用模式。
-
最常用模式為?StartNew,它在一個語句中創(chuàng)建并啟動任務(wù)。
-
如果通過多個先行任務(wù)創(chuàng)建延續(xù)任務(wù),請使用?ContinueWhenAll?方法或?ContinueWhenAny?方法,或它們在?Task<TResult>?類中的相當(dāng)方法。。
-
若要在?
BeginX
?或?EndX
?實例中封裝異步編程模型?Task?和?Task<TResult>?方法,請使用?FromAsync?方法。
默認的?TaskFactory?可作為?Task?類或?Task<TResult>?類上的靜態(tài)屬性訪問。 你還可以直接實例化?TaskFactory?并指定各種選項,包括?CancellationToken、TaskCreationOptions?選項、TaskContinuationOptions?選項或?TaskScheduler。 創(chuàng)建任務(wù)工廠時所指定的任何選項將應(yīng)用于它創(chuàng)建的所有任務(wù),除非?Task?是通過使用?TaskCreationOptions?枚舉創(chuàng)建的(在這種情況下,任務(wù)的選項重寫任務(wù)工廠的選項)。
14、無委托的任務(wù)
在某些情況下,可能需要使用?Task?封裝由外部組件(而不是用戶委托)執(zhí)行的某個異步操作。 如果該操作基于異步編程模型 Begin/End 模式,你可以使用?FromAsync?方法。 如果不是這種情況,你可以使用?TaskCompletionSource<TResult>?對象將該操作包裝在任務(wù)中,并因而獲得?Task?可編程性的一些好處。 例如對異常傳播和延續(xù)的支持。?
15、自定義計劃程序
大多數(shù)應(yīng)用程序或庫開發(fā)人員并不關(guān)心任務(wù)在哪個處理器上運行、任務(wù)如何將其工作與其他任務(wù)同步以及如何在?System.Threading.ThreadPool?中計劃任務(wù)。 他們只需要它在主機上盡可能高效地執(zhí)行。 如果需要對計劃細節(jié)進行更細化的控制,可以使用 TPL 在默認任務(wù)計劃程序上配置一些設(shè)置,甚至是提供自定義計劃程序。?
16、相關(guān)數(shù)據(jù)結(jié)構(gòu)
TPL 有幾種在并行和順序方案中都有用的新公共類型。 其中包括?System.Collections.Concurrent?命名空間中的多個線程安全、快速且可縮放的集合類,以及多個新的同步類型。 例如?System.Threading.Semaphore?和?System.Threading.ManualResetEventSlim,對于特定類型的工作負載,兩者在效率方面超過了原有類型。 .NET Framework 4 中的其他新類型(例如?System.Threading.Barrier?和?System.Threading.SpinLock)提供了早期版本中未提供的功能。?
17、自定義任務(wù)類型
建議不要從?System.Threading.Tasks.Task?或?System.Threading.Tasks.Task<TResult>?繼承。 相反,我們建議你使用?AsyncState?屬性將其他數(shù)據(jù)或狀態(tài)與?Task?或?Task<TResult>?對象相關(guān)聯(lián)。 還可以使用擴展方法擴展?Task?和?Task<TResult>?類的功能。 有關(guān)擴展方法的詳細信息,請參閱擴展方法和擴展方法。
如果必須從?Task?或?Task<TResult>?繼承,則不能使用?Run?或?System.Threading.Tasks.TaskFactory,System.Threading.Tasks.TaskFactory<TResult>?或?System.Threading.Tasks.TaskCompletionSource<TResult>?類創(chuàng)建自定義任務(wù)類型的實例。 不能使用是因為這些類僅創(chuàng)建?Task?和?Task<TResult>?對象。 此外,不能使用?Task、Task<TResult>、TaskFactory?和?TaskFactory<TResult>?提供的任務(wù)延續(xù)機制創(chuàng)建自定義任務(wù)類型的實例。 不能使用也是因為這些類僅創(chuàng)建?Task?和?Task<TResult>?對象。