日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

C#的变迁史06 - C# 4.0 之并行处理篇

發布時間:2023/12/10 C# 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C#的变迁史06 - C# 4.0 之并行处理篇 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  前面看完了Task對象,這里再看一下另一個息息相關的對象Parallel。

Parallel對象

  Parallel對象封裝了能夠利用多核并行執行的多線程操作,其內部使用Task來分裝多線程的任務并試圖將它們分配到不同的內核中并行執行。請注意“試圖”這個詞,Parallel對象相當具有智能性,當它判斷任務集并沒有從并行運行中受益,就會選擇按順序運行。這樣的做法是因為并非所有的項目都適合使用并行開發,創建過多并行任務可能會損害程序的性能,降低運行效率。

  Parallel對象是靜態類,它主要有3個靜態方法:Invoke,For,ForEach。針對這3個方法,該對象也提供了多種不同的重載方法,使用起來相當的簡單。先看一個簡單的例子:

static void Main(string[] args) {Parallel.Invoke(()=>Console.WriteLine("1st task!"),()=>Console.WriteLine("2nd task!")); }

  這個例子中的兩個任務就是并行執行的,所以結果可能是第一個先完成,也可能是第二個先輸出結果。是不是超級簡單?有沒有使用一下Parallel對象的沖動?

  下面這個網上的例子驗證了一下運行時間上并行計算的優越性:

private const int count = 1000000000; private static void M1() {Console.WriteLine("M1 is busy now");for (int i = 0; i < count; i++);Console.WriteLine("M1 is Done"); } private static void M2() {Console.WriteLine("M2 is busy now");for (int i = 0; i < count; i++);Console.WriteLine("M2 is Done"); } static void Main(string[] args) {// 順序執行DateTime start1 = DateTime.Now;M1();M2();Console.WriteLine(DateTime.Now - start1);// 并行執行DateTime start2 = DateTime.Now;Parallel.Invoke(M1, M2);Console.WriteLine(DateTime.Now - start2); }

  在不同的機器上,得到的結果可能不同,但是基本上所有的多核機器上得到的結果一定是并行執行的時候耗時比較短,例子比較簡單,但是道理確實很直接。

  通常來說,對于一個程序,性能提升的關鍵是將可以并行執行的同步程序改成并行執行。這個上面的例子也反應了修改后的效果。此外,對于程序來說,循環是影響復雜度的最直接的因素,這個我們看看教科書上計算算法時間復雜度的算法就知道了,所以提升循環的執行效率往往是提升程序效率的關鍵一步。Parallel對象充分考慮到了這一點,提供了循環的并行版本。

例子一:For循環。

static void Main(string[] args) {for (int i = 0; i < 10; i++) Console.Write("{0} ", i);Console.WriteLine("by serial");Parallel.For(0, 10, (n) => Console.Write("{0} ", n));Console.WriteLine("by parallel"); }

  從輸出的結果你可以很容易發現后面的結果順序完全是不固定的,這是并行的特征。

例子二:ForEach循環?

static void Main(string[] args) {int [] a = {1,2,3,4,5,6,7,8,9};foreach (var n in a) Console.Write("{0} ",n);Console.WriteLine("by serial");Parallel.ForEach(a, (n) => Console.Write("{0} ", n));Console.WriteLine("by parallel"); }

  結果也很明顯,就不多說了。

  通過上面的兩個例子,其實我們就能發現一些問題:

1. 順序要求嚴格的操作不能使用Parallel對象的方法,這個原因很簡單。

2.?并不是所有的for語句都可以用并行處理來實行,只有在循環開始前循環的次數已確定的情況下可以采用并行處理。同理,do語句和while語句也不能采用并行處理。因為所謂“并行”就是在判定為“循環結束”之前,首先要把將要執行的循環實現分配好。

  好了,既然是對循環的并行處理,那就避不開break與continue的問題,也就是循環的主動中止問題。

循環的主動中止

  在Parallel對象中,也可以主動中止循環的執行:調用ParallelLoopState實例的Stop方法和Break方法,可以停止和中斷當前循環的執行。其中,

1. Break 告知 Parallel 循環應在系統方便的時候盡早停止執行當前迭代之外的迭代,當前迭代之前的迭代任然會完成。
2. Stop 告知 Parallel 循環應在系統方便的時候盡早停止執行,不管其他的線程執行到什么程度。
  通常使用Stop會立即停止循環,使用Break卻會執行完畢當前迭代次序前面的迭代后停止循環。例如,對于從 0 到 1000 并行迭代的 for 循環,如果從第 100 此迭代開始調用 Break,則低于 100 的所有迭代仍會運行,從 101 到 1000 的迭代則不一定會執行,注意是“不一定”,因為是并行執行的,說不定某些次序在后面的迭代已經執行了。看一下例子:

static void Main(string[] args) {DemoStop();DemoBreak(); }/// <summary> /// 中斷Stop /// </summary> static void DemoStop() {List<int> data = new List<int>(){ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};Parallel.For(0, data.Count, (i, LoopState) =>{if (i > 5)LoopState.Stop();Thread.Sleep(500);Console.WriteLine(i);});Console.WriteLine("Stop執行結束。"); } /// <summary> /// 中斷Break /// </summary> static void DemoBreak() {List<int> data = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};Parallel.ForEach(data, (i, LoopState) =>{if (i > 5)LoopState.Break();Thread.Sleep(500);Console.WriteLine(i);});Console.WriteLine("Break執行結束。"); }

  運行一下,對比結果,細細體會一下輸出的結果,我想你就會清楚Stop方法與Break方法的區別。

  當然了,前面講的使用CancellationTokenSource取消線程的方式這里任然是適用的,不過需要通過ParallelOptions傳給Parallel對象對應的重載方法。ParallelOptions對象還可以配置其他的一些參數,比如最大的并行數量(其實就是使用的最大內核數量)等等。看一個簡單的例子:

CancellationTokenSource token = new CancellationTokenSource(); Task.Factory.StartNew(() => {Thread.Sleep(5000);token.Cancel();Console.WriteLine("Token Cancelled."); });ParallelOptions loopOptions = new ParallelOptions() {CancellationToken = token.Token,MaxDegreeOfParallelism = 2 };try {Parallel.For(0, Int64.MaxValue, loopOptions, i =>{Console.WriteLine("i={0},thread id={1}", i, Thread.CurrentThread.ManagedThreadId);Thread.Sleep(1000);}); } catch (OperationCanceledException) {Console.WriteLine("Exception..."); }

  討論完了各種正常情況,下面來看一下不正常的情況:異常問題。

異常問題

  和普通的for/foreach中發生異常的表現一樣,Parallel循環中的任何異常都會使整個循環終止,不過由于整個循環是分核同時進行的,因此整個循環不會立即終止,這個很好理解。循環中停止前所有的異常都會被封裝在AggregateException的InnerExceptions中。捕獲這些異常的方式很簡單,使用try/catch就可以了,看一下下面的代碼:

try {Parallel.For(0, 5, (i) =>{throw new Exception(i.ToString());}); } catch (AggregateException ae) {foreach (var exp in ae.InnerExceptions){Console.WriteLine(exp.Message);} }

  這段代碼將會輸出0-4的子集(也有可能是0-4全部輸出,因為5個線程都很快)。

  不過,與Parallel.For和ForEach不一樣的是,Parallel.Invoke總是會把所有任務都執行完,然后把所有的異常包裝在AggregateException中。其實道理與上面的循環是一樣的,都是把應該執行的任務執行完,來看這段代碼:

try {Parallel.Invoke(() => { throw new Exception("1"); },() => { Thread.Sleep(1500); throw new Exception("2"); },() => { Thread.Sleep(3000); throw new Exception("3"); }); } catch (AggregateException ae) {foreach (var ex in ae.InnerExceptions){Console.WriteLine(ex.Message);} }

  結果會輸出:3 2 1。

  除此以外,Task.WaitAll和Parallel.Invoke是類似,任何一個(或多個)Task的異常不會影響任何其他Task的執行。

try {var t1 = Task.Factory.StartNew(() =>{Thread.Sleep(500);throw new Exception("1");});var t2 = Task.Factory.StartNew(() =>{Thread.Sleep(1000);throw new Exception("2");});Task.WaitAll(t1, t2); } catch (AggregateException ae) {foreach (var exp in ae.InnerExceptions){Console.WriteLine(exp.Message);} }

  這段代碼會輸出:1 2。

  兩個異常都會在AggregateException中的InnerExceptions屬性中。不過很顯然異常的順序與上一個例子有點不同,這個需要注意一點。

  其實,在新的.NET類庫中,不僅通過增加Parallel對象來增強并行處理的能力,而且在Linq語句中也有相應的增強,那就是PLinq。

PLinq簡介

  PLINQ也就是Parallel Linq,它的使用方法是非常簡單。
  下例本身沒有什么太大意義,只不過是找出“2”,然后輸出:

using System; using System.Linq; using System.Threading.Tasks; class Program {static void Main(string[] args){int[] ar = { 1, 2, 3 };var q1 = from n in arwhere n == 2select n;foreach (var n in q1){Console.WriteLine("found {0}", n);}} }

如果把上例改成用并行處理,只要在查詢表達式中追加AsParallel方法就可以了:

var q1 = from n in ar.AsParallel()where n == 2select n;

函數形式也是一樣的。例如下面這個查詢表達式:

var q1 = ar.Where((c) => c == 2);

改成并行執行也就是插入AsParallel方法就可以了:

var q1 = ar.AsParallel().Where((c) => c == 2);

  使用PLINQ是如此的簡單,只要用一個方法就可以用并行來處理查詢表達式了。但是,正如前面所講的并行計算并不是適用于任何場合的靈丹妙藥,它也有不太適用的場合:
1. 在大量使用查詢表達式的時候,并不是每一句查詢表達式都是性能瓶頸的關鍵,如果每一個查詢表達式都插入AsParallel方法,不會帶來太大好處,在浪費時間的同時,代碼的可讀性也降低了。
2. 插入AsParallel方法后,結果會發生變化,這個自然很好理解,因為并行執行了嘛,順序得不到保證,所以與順序有關的操作是適合使用同步操作的,并行執行就可能導致問題。


  其實AsParallel方法只是PLinq的基本入口點,在System.Linq.ParallelEnumerable類中,包含了并行查詢的大部分其他有用的方法,比如:AsSequential(指定查詢的其余部分應像非并行 LINQ 查詢一樣按順序運行),AsOrdered(指定 PLINQ 應保留查詢的其余部分的源序列排序,直到例如通過使用 orderby子句更改排序為止),AsUnordered(指定查詢的其余部分的 PLINQ 不需要保留源序列的排序)等等方法。這個查看一下MSDN就可以了,使用起來還是比較方便的。也可查看博客園中的一些詳細的文章,比如:http://www.cnblogs.com/leslies2/archive/2012/02/07/2320914.html。

?

  并行計算就簡單總結這些了,銘記一點:并行執行的任務要保證是順序無關的,獨立的。

總結

以上是生活随笔為你收集整理的C#的变迁史06 - C# 4.0 之并行处理篇的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。