菜鳥學習並行編程,參考《C#並行編程高級教程.PDF》,如有錯誤,歡迎指正。
任務簡介
TPL引入新的基於任務的編程模型,通過這種編程模型可以發揮多核的功效,提升應用程序的性能,不需要編寫底層復雜且重量級的線程代碼。
但需要注意:任務並不是線程(任務運行的時候需要使用線程,但並不是說任務取代了線程,任務代碼是使用底層的線程(軟件線程,調度在特定的硬件線程或邏輯內核上)運行的,任務與線程之間並沒有一對一的關系。)
創建一個新的任務時,調度器(調度器依賴於底層的線程池引擎)會使用工作竊取隊列找到一個最合適的線程,然後將任務加入隊列,任務所包含的代碼會在一個線程中運行。如圖:
System.Threading.Tasks.Task
一個Task表示一個異步操作,Task提供了很多方法和屬性,通過這些方法和屬性能夠對Task的執行進行控制,並且能夠獲得其狀態信息。
Task的創建和執行都是獨立的,因此可以對關聯操作的執行擁有完全的控制權。
使用Parallel.For、Parallel.ForEach的循環迭代的並行執行,TPL會在後台創建System.Threading.Tasks.Task的實例。
使用Parallel.Invoke時,TPL也會創建與調用的委托數目一致的System.Threading.Tasks.Task的實例。
注意項
程序中添加很多異步的操作作為Task實例加載的時候,為了充分利用運行時所有可用的邏輯內核,任務調度器會嘗試的並行的運行這些任務,也會嘗試在所有的可用內核上對工作進行負載均衡。
但在實際的編碼過程當中,並不是所有的代碼片段都能夠方便的用任務來運行,因為任務會帶來額外的開銷,盡管這種開銷比添加線程所帶來的開銷要小,但是仍然需要將這個開銷考慮在內。
Task狀態與生命周期
一個Task實例只會完成其生命周期一次,當Task到達它的3種肯呢過的最終狀態之一是,就無法回到之前的任何狀態
class Program
{
/* coder:釋迦苦僧 */
static void Main(string[] args)
{
/* 創建一個任務 不調用 不執行 狀態為Created */
Task tk = new Task(() =>
{
});
Console.WriteLine(tk.Status.ToString());
/* 創建一個任務 執行 狀態為 WaitingToRun */
Task tk1 = new Task(() =>
{
});
tk1.Start();/*對於安排好的任務,就算調用Start方法也不會立馬啟動 此時任務的狀態為WaitingToRun*/
Console.WriteLine(tk1.Status.ToString());
/* 創建一個主任務 */
Task mainTask = new Task(() =>
{
SpinWait.SpinUntil(() =>
{
return false;
}, 30000);
});
/* 將子任務加入到主任務完成之後執行 */
Task subTask = mainTask.ContinueWith((t1) =>
{
});
/* 啟動主任務 */
mainTask.Start();
/* 此時子任務狀態為 WaitingForActivation */
Console.WriteLine(subTask.Status.ToString());
/* 創建一個任務 執行 後 等待一段時間 並行未結束的情況下 狀態為 Running */
Task tk2 = new Task(() =>
{
SpinWait.SpinUntil(() => false, 30000);
});
tk2.Start();/*對於安排好的任務,就算調用Start方法也不會立馬啟動*/
SpinWait.SpinUntil(() => false, 300);
Console.WriteLine(tk2.Status.ToString());
/* 創建一個任務 然後取消該任務 狀態為Canceled */
CancellationTokenSource cts = new CancellationTokenSource();
Task tk3 = new Task(() =>
{
for (int i = 0; i < int.MaxValue; i++)
{
if (!cts.Token.IsCancellationRequested)
{
cts.Token.ThrowIfCancellationRequested();
}
}
}, cts.Token);
tk3.Start();/*啟動任務*/
SpinWait.SpinUntil(() => false, 100);
cts.Cancel();/*取消該任務執行 但並非立馬取消 所以對於Canceled狀態也不會立馬生效*/
SpinWait.SpinUntil(() => false, 1000);
Console.WriteLine(tk3.Status.ToString() + " " + tk3.IsCanceled);
SpinWait.SpinUntil(() => false, 1000);
Console.WriteLine(tk3.Status.ToString() + " " + tk3.IsCanceled);
SpinWait.SpinUntil(() => false, 1000);
Console.WriteLine(tk3.Status.ToString() + " " + tk3.IsCanceled);
/*創建一個任務 讓它成功的運行完成 會得到 RanToCompletion 狀態*/
Task tk4 = new Task(() =>
{
SpinWait.SpinUntil(() => false, 10);
});
tk4.Start();
SpinWait.SpinUntil(() => false, 300);
Console.WriteLine(tk4.Status.ToString());
/*創建一個任務 讓它運行失敗 會得到 Faulted 狀態*/
Task tk5 = new Task(() =>
{
throw new Exception();
});
tk5.Start();
SpinWait.SpinUntil(() => false, 300);
Console.WriteLine(tk5.Status.ToString());
Console.ReadLine();
}
}
class Product
{
public string Name { get; set; }
public string Category { get; set; }
public int SellPrice { get; set; }
}
View Code
使用任務來對代碼進行並行化
使用Parallel.Invoke可以並行加載多個方法,使用Task實例也能完成同樣的工作,下面貼代碼:
等待任務完成Task.WaitAll
Task.WaitAll 方法,這個方法是同步執行的,在Task作為參數被接受,所有Task結束其執行前,主線程不會繼續執行下一條指令,下面貼代碼
Task.WaitAll 限定等待時長
如圖10毫秒沒有完成任務,則輸出了****
通過取消標記取消任務
通過取消標記來中斷Task實例的執行。 CancellationTokenSource,CancellationToken下的IsCanceled屬性標志當前是否已經被取消,取消任務,任務也不一定會馬上取消,下面貼代碼:
Task異常處理 當很多任務並行運行的時候,可能會並行發生很多異常。Task實例能夠處理一組一組的異常,這些異常有System.AggregateException類處理
Task返回值 Task<TResult>
class Program
{
/* coder:釋迦苦僧 */
static void Main(string[] args)
{
/*創建任務t1*/
Task t1 = Task.Factory.StartNew(() =>
{
Console.WriteLine("執行 t1 任務");
SpinWait.SpinUntil(() =>
{
return false;
}, 2000);
});
/*創建任務t2 t2任務的執行 依賴與t1任務的執行完成*/
Task t2 = t1.ContinueWith((t) =>
{
Console.WriteLine("執行 t2 任務");
SpinWait.SpinUntil(() =>
{
return false;
}, 2000);
});
/*創建任務t3 t3任務的執行 依賴與t2任務的執行完成*/
Task t3 = t2.ContinueWith((t) =>
{
Console.WriteLine("執行 t3 任務");
});
Console.ReadLine();
}
}
View Code
TaskContinuationOptions
TaskContinuationOptions參數,可以控制延續另一個任的任務調度和執行的可選行為。下面看代碼:
TaskContinuationOptions 屬性有很多,如下所示
關於並行編程中的Task就寫到這,如有問題,請指正。
->是一個整體,它是用於指向結構體、C++中的class等含有子數據的指針用來取子數據。換種說法,如果我們在C語言中定義了一個結構體,然後申明一個指針指向這個結構體,那麼我們要用指針取出結構體中的數據,就要用到“->”.
舉個例子:
struct Data
{
int a,b,c;
}; /*定義結構體*/
struct Data * p;/*定義結構體指針*/
struct Data A = {1,2,3};/*聲明變量A*/
int x;/*聲明一個變量x*/
p = &A ; /*讓p指向A*/
x = p->a;/*這句話的意思就是取出p所指向的結構體中包含的數據項a賦值給x*/
/*由於此時p指向A,因而 p->a == A.a,也就是1*/
對於一開始的問題 p = p->next;這應該出現在C語言的鏈表,這裡的next應該是一個與p同類型的結構體指針,其定義格式應該是:
struct Data
{
int a;
struct Data * next;
};/*定義結構體*/
…………
main()
{
struct Data * p;/*聲明指針變量p*/
……
p = p->next;/*將next中的值賦給p*/
}
鏈表指針是C語言的一個難點,但也是重點,學懂了非常有用。要仔細講就必須先講變量、指針。
什麼是變量?所謂變量,不要淺顯的認為會變得量就是變量。套用我們院長的問話:“教室變不變?”變,因為每天有不同的人在裡面上課,但又不變,因為教室始終在那,沒有變大或變小。這就是變量:有一個不變的地址和一塊可變的存儲空間。正常情況下,我們只看到變量這個房間裡面的東西,也就是其內容,但不會關注變量的地址,但是C語言的指針,就是這個房間的地址。我們聲明變量就相當於蓋了間房子存放東西,我們可以直接觀看房子裡的東西,而聲明指針,就是相當於獲得了一個定位器,當用指針指向某個變量時,就是用指針給變量定位,以後我們就可以用指針找到他所“跟蹤”的變量並可以獲得裡面的內容。
那結構體呢?結構體就相當於是有好幾個房子組成的別墅,幾個房子綁定在一起使用。假設現在有很多這種別墅分布在一個大迷宮裡,每間別墅裡都有一間房子。裡面放了另一個別墅的位置信息,現在你手拿定位器找到了第一棟別墅,從裡面得到了你想要的東西(鏈表的數據部分),然後把下一棟別墅的位置計入你的定位器(p = p->next),再走向下一棟別墅……如此走下去,知道走到某地下一棟別墅信息沒有了(p->next == NULL),你的旅行結束。這就是鏈表一次遍歷的過程。現在你能明白 p=p->next的含義了吧!
寫了這麼多。希望你能明白。
如果想學好c和C++,鏈表和指針必須熟練掌握!
->是一個整體,它是用於指向結構體、C++中的class等含有子數據的指針用來取子數據。換種說法,如果我們在C語言中定義了一個結構體,然後申明一個指針指向這個結構體,那麼我們要用指針取出結構體中的數據,就要用到“->”.
舉個例子:
struct Data
{
int a,b,c;
}; /*定義結構體*/
struct Data * p;/*定義結構體指針*/
struct Data A = {1,2,3};/*聲明變量A*/
int x;/*聲明一個變量x*/
p = &A ; /*讓p指向A*/
x = p->a;/*這句話的意思就是取出p所指向的結構體中包含的數據項a賦值給x*/
/*由於此時p指向A,因而 p->a == A.a,也就是1*/
對於一開始的問題 p = p->next;這應該出現在C語言的鏈表,這裡的next應該是一個與p同類型的結構體指針,其定義格式應該是:
struct Data
{
int a;
struct Data * next;
};/*定義結構體*/
…………
main()
{
struct Data * p;/*聲明指針變量p*/
……
p = p->next;/*將next中的值賦給p*/
}
鏈表指針是C語言的一個難點,但也是重點,學懂了非常有用。要仔細講就必須先講變量、指針。
什麼是變量?所謂變量,不要淺顯的認為會變得量就是變量。套用我們院長的問話:“教室變不變?”變,因為每天有不同的人在裡面上課,但又不變,因為教室始終在那,沒有變大或變小。這就是變量:有一個不變的地址和一塊可變的存儲空間。正常情況下,我們只看到變量這個房間裡面的東西,也就是其內容,但不會關注變量的地址,但是C語言的指針,就是這個房間的地址。我們聲明變量就相當於蓋了間房子存放東西,我們可以直接觀看房子裡的東西,而聲明指針,就是相當於獲得了一個定位器,當用指針指向某個變量時,就是用指針給變量定位,以後我們就可以用指針找到他所“跟蹤”的變量並可以獲得裡面的內容。
那結構體呢?結構體就相當於是有好幾個房子組成的別墅,幾個房子綁定在一起使用。假設現在有很多這種別墅分布在一個大迷宮裡,每間別墅裡都有一間房子。裡面放了另一個別墅的位置信息,現在你手拿定位器找到了第一棟別墅,從裡面得到了你想要的東西(鏈表的數據部分),然後把下一棟別墅的位置計入你的定位器(p = p->next),再走向下一棟別墅……如此走下去,知道走到某地下一棟別墅信息沒有了(p->next == NULL),你的旅行結束。這就是鏈表一次遍歷的過程。現在你能明白 p=p->next的含義了吧!
寫了這麼多。希望你能明白。
如果想學好c和C++,鏈表和指針必須熟練掌握!