C#中的thread和task之Task
簡介
Task是在.NET Framework 4中新增進來的,這是新的namespace:System.Threading.Tasks
;它強調的是adding parallelism and concurrency to applications
。
現在都是多核的CPU,在系統內,Task Parallel Library能更有效地利用CPU核心。TPL 會動態地按比例調節併發程度,以便最有效地使用所有可用的處理器。TPL 還處理工作分割槽、ThreadPool 上的執行緒排程、取消支援、狀態管理以及其他低級別的細節操作。對比ThreadPool上的Thread並沒有很好地支援Cancel的操作。
在語法上,和lambda表示式更好地結合。
建立Task
顯式建立Task
先看一個簡單的示例:
using System;
using System.Threading;
using System.Threading.Tasks;
public class TaskExample1
{
public static void Main()
{
Thread.CurrentThread.Name = "Main";
// 使用lambda方式,之間提供一個使用者delegate,建立一個Task
Task taskA = new Task( () => Console.WriteLine("From taskA."));
// Start
taskA.Start();
// Output a message from the calling thread.
Console.WriteLine("From thread '{0}'.",
Thread.CurrentThread.Name);
//等待task結束
taskA.Wait();
}
}
// From thread 'Main'.
// From taskA.
任務物件Task提供可在任務的整個生存期內從呼叫執行緒訪問的方法和屬性。例如,可以隨時訪問任務的 Status 屬性,以確定它是已開始執行、已完成執行、已取消還是引發了異常。狀態由 TaskStatus 列舉表示。
簡化建立並開始的Task操作版本–直接使用一個操作: System.Threading.Tasks.Task.Run()
using System;
using System.Threading;
using System.Threading.Tasks;
public class TaskExample2
{
public static void Main()
{
Thread.CurrentThread.Name = "Main";
// Define and run the task.
Task taskA = Task.Run( () => Console.WriteLine("From taskA."));
// Output a message from the calling thread.
Console.WriteLine("From thread '{0}'.",
Thread.CurrentThread.Name);
taskA.Wait();
}
}
// From thread 'Main'.
// From taskA.
TaskFactory建立Task
TPL提供了工廠類TaskFactory,也可用直接建立Task。 一個操作System.Threading.Tasks.TaskFactory.StartNew
, 即可完成建立並開始一個Task.
Use this method when creation and scheduling do not have to be separated and you require additional task creaion options or the use of a specific scheduler, or when you need to pass additional state into the task through its AsyncState property, as shown in the following example.
using System;
using System.Threading;
using System.Threading.Tasks;
public class TaskExample3
{
public static void Main()
{
Thread.CurrentThread.Name = "Main";
// 建立task並啟動
Task taskA = Task.Factory.StartNew(() => Console.WriteLine("From taskA."));
//
Console.WriteLine("From thread '{0}'.",
Thread.CurrentThread.Name);
// wait task
taskA.Wait();
}
}
// Hello from thread 'Main'.
// Hello from taskA.
傳入引數
來自MS的示例:
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (int param) => {
Console.WriteLine("Task #{0}.", param);
},
i );
}
Task.WaitAll(taskArray);
}
}
Task返回值
如果一個Task執行方法是有返回值的,可用得到其值。在建立一個task時,其返回的值為Task,表示一個返回型別為T的Task;在taks完成執行後,可用通過Task的Result屬性獲取結果。
public class Task<TResult> : System.Threading.Tasks.Task
來自MS的示例:
using System;
using System.Linq;
using System.Threading.Tasks;
class Program
{
static void Main()
{
// lambda表示式建立一個task並執行,並返回值。
Task<int> task1 = Task<int>.Factory.StartNew(() => 1);
int i = task1.Result;
// 返回物件
Task<Test> task2 = Task<Test>.Factory.StartNew(() =>
{
string s = ".NET";
double d = 4.0;
return new Test { Name = s, Number = d };
});
Test test = task2.Result;
// Return an array produced by a PLINQ query
Task<string[]> task3 = Task<string[]>.Factory.StartNew(() =>
{
string path = @"C:\Users\Public\Pictures\Sample Pictures\";
string[] files = System.IO.Directory.GetFiles(path);
var result = (from file in files.AsParallel()
let info = new System.IO.FileInfo(file)
where info.Extension == ".jpg"
select file).ToArray();
return result;
});
foreach (var name in task3.Result)
Console.WriteLine(name);
}
class Test
{
public string Name { get; set; }
public double Number { get; set; }
}
}
Continue操作
如果一個Task開始執行時間依賴於其它的Task的完成,可以使用Continue系列方法。
來自MS的示例:
using System;
using System.Threading.Tasks;
public class Example
{
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);
}
}
// 輸出:
// N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
而且,可以使用更簡化的寫法.
using System;
using System.Threading.Tasks;
public class Example
{
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);
}
}
Cancel操作
在TPL中,為Task能cancel執行,提供了CancellationTokenSource和CancellationToken;
需要完成的工作是:在Task的action執行方法內,週期性地檢查CancellationToken的IsCancellationRequested屬性。
示例:
private var tokenSource = new CancellationTokenSource();
public void Start()
{
var token = tokenSource.Token;
for (int i = 0; i<5; i++>)
{
Task t = Task.Factory.StartNew( () => DoSomeWork(i, token), token);
Console.WriteLine("Task {0} executing", t.Id);
}
}
public void Stop()
{
var token = tokenSource.Token;
tokenSource.Cancel();
}
void DoSomeWork(int taskNum, CancellationToken ct)
{
// 先檢查,排程進入時,是否cancel了。
if (ct.IsCancellationRequested == true) {
Console.WriteLine("Task {0} was cancelled before it got started.",
taskNum);
ct.ThrowIfCancellationRequested(); // 丟擲異常-- 或者 return
}
// 正式開始任務。
int maxIterations = 100;
// NOTE!!! A "TaskCanceledException was unhandled
for (int i = 0; i <= maxIterations; i++) {
//
var sw = new SpinWait();
for (int j = 0; j <= 100; j++)
sw.SpinOnce();
if (ct.IsCancellationRequested) {
Console.WriteLine("Task {0} cancelled", taskNum);
ct.ThrowIfCancellationRequested(); //丟擲異常-- 或者 return
}
}
}