在了解Task用法之前就不得不先對線程有一定的了解
線程的創建
static void Main(){
new Thread(Go).Start(); // new 一個Thread后要調用Start() 線程才會開始執行
Task.Factory.StartNew(Go); //立即創建並執行
Task.Run(new Action(Go)); // 立即創建並執行
}
public static void Go(){
Console.WriteLine("我是另一個線程");
}
線程的創建是比較占用資源,所以有了線程池,new 一個Thread 不會通過線程池(當然也可以使用ThreadPool),Task默認直接使用線程池
static void Main() {
Console.WriteLine("我是主線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
ThreadPool.QueueUserWorkItem(Go);
Console.ReadLine();
}
public static void Go(object data) {
Console.WriteLine("我是另一個線程:Thread Id {0}",Thread.CurrentThread.ManagedThreadId);
}
傳入參數
static void Main() {
new Thread(Go).Start("arg1"); // 沒有匿名委托之前,我們只能這樣傳入一個object的參數
new Thread(delegate(){ // 有了匿名委托之后...
GoGoGo("arg1", "arg2", "arg3");
});
new Thread(() => { // 當然,還有 Lambada
GoGoGo("arg1","arg2","arg3");
}).Start();
Task.Run(() =>{ // Task能這么靈活,也是因為有了Lambda呀。
GoGoGo("arg1", "arg2", "arg3");
});
}
public static void Go(object name){
// TODO
}
public static void GoGoGo(string arg1, string arg2, string arg3){
// TODO
}
返回值
Thead是不能返回值的,但是作為更高級的Task當然要彌補一下這個功能
static void Main() {
// GetDayOfThisWeek 運行在另外一個線程中
var dayName = Task.Run<string>(() => { return GetDayOfThisWeek(); });
Console.WriteLine("今天是:{0}",dayName.Result);
}
共享數據以及線程安全
線程直接可以通過靜態變量來共享數據
private static bool _isDone = false;
private static object _lock = new object();
static void Main(){
new Thread(Done).Start();
new Thread(Done).Start();
Console.ReadLine();
}
static void Done(){
lock (_lock){
if (!_isDone){
Console.WriteLine("Done"); // 猜猜這里面會被執行幾次?
_isDone = true;
}
}
}
lock的資源沒有釋放之前其他線程是無法訪問其中的代碼塊的,_lock 是靜態的,所以,當一個線程進入后,其他的線程都需要等待
上述例子通過了靜態變量實現了2個線程之間的數據共享
Semaphore
它可以控制對某一段代碼或者對某個資源訪問的線程的數量,超過這個數量之后,其它的線程就得等待,只有等現在有線程釋放了之后,下面的線程才能訪問。這個跟鎖有相似的功能,只不過不是獨占的,它允許一定數量的線程同時訪問。
static SemaphoreSlim _sem = new SemaphoreSlim(3); // 我們限制能同時訪問的線程數量是3
static void Main(){
for (int i = 1; i <= 5; i++) new Thread(Enter).Start(i);
Console.ReadLine();
}
static void Enter(object id){
Console.WriteLine(id + " 開始排隊...");
_sem.Wait();
Console.WriteLine(id + " 開始執行!");
Thread.Sleep(1000 * (int)id);
Console.WriteLine(id + " 執行完畢,離開!");
_sem.Release();
}
異常處理
在使用Thread時,子線程中的異常,主線程是捕獲不到的,但是Task是可以的
public static void Main(){
try{
var task = Task.Run(() => { Go(); });
task.Wait(); // 在調用了這句話之后,主線程才能捕獲task里面的異常
// 對於有返回值的Task, 我們接收了它的返回值就不需要再調用Wait方法了
// GetName 里面的異常我們也可以捕獲到
var task2 = Task.Run(() => { return GetName(); });
var name = task2.Result;
}
catch (Exception ex){
Console.WriteLine("Exception!");
}
}
static void Go() { throw null; }
static string GetName() { throw null; }