5天玩轉C#並行和多執行緒程式設計系列文章目錄
5天玩轉C#並行和多執行緒程式設計 —— 第一天 認識Parallel
5天玩轉C#並行和多執行緒程式設計 —— 第二天 並行集合和PLinq
5天玩轉C#並行和多執行緒程式設計 —— 第三天 認識和使用Task
5天玩轉C#並行和多執行緒程式設計 —— 第四天 Task進階
5天玩轉C#並行和多執行緒程式設計 —— 第五天 多執行緒程式設計大總結
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections.Concurrent; namespace ThreadPool { public class PEnumerable { public static void ListWithParallel() { List<int> list = new List<int>(); Parallel.For(0, 10000, item => { list.Add(item); }); Console.WriteLine("List's count is {0}",list.Count()); } } }
點選F5執行,得到如下結果:
看到結果中顯示的5851,但是我們迴圈的是10000次啊!怎麼結果不對呢?這是因為List<T>是非執行緒安全集合,意思就是說所有的執行緒都可以修改他的值。
下面我們來看下並行集合 —— 執行緒安全集合,在System.Collections.Concurrent名稱空間中,首先來看一下ConcurrentBag<T>泛型集合,其用法和List<T>類似,先來寫個方法測試一下:
public static void ConcurrentBagWithPallel() { ConcurrentBag<int> list = new ConcurrentBag<int>(); Parallel.For(0, 10000, item => { list.Add(item); }); Console.WriteLine("ConcurrentBag's count is {0}", list.Count()); }
同時執行兩個方法,結果如下:
可以看到,ConcurrentBag集合的結果是正確的。下面我們修改程式碼看看ConcurrentBag裡面的資料到底是怎麼存放的,修改程式碼如下:
public static void ConcurrentBagWithPallel() { ConcurrentBag<int> list = new ConcurrentBag<int>(); Parallel.For(0, 10000, item => { list.Add(item); }); Console.WriteLine("ConcurrentBag's count is {0}", list.Count()); int n = 0; foreach(int i in list) { if (n > 10) break; n++; Console.WriteLine("Item[{0}] = {1}",n,i); } Console.WriteLine("ConcurrentBag's max item is {0}", list.Max()); }
先來看一下執行結果:
可以看到,ConcurrentBag中的資料並不是按照順序排列的,順序是亂的,隨機的。我們平時使用的Max、First、Last等linq方法都還有。其時分類似Enumerable的用法,大家可以參考微軟的MSDN瞭解它的具體用法。
關於執行緒安全的集合還有很多,和我們平時用的集合都差不多,比如類似Dictionary的ConcurrentDictionary,還有ConcurrentStack,ConcurrentQueue等。
1、AsParallel
前面瞭解了並行的For和foreach,今天就來看一下Linq的並行版本是怎麼樣吧?為了測試,我們新增一個Custom類,程式碼如下:
public class Custom { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } }
寫如下測試程式碼:
public static void TestPLinq() { Stopwatch sw = new Stopwatch(); List<Custom> customs = new List<Custom>(); for (int i = 0; i < 2000000; i++) { customs.Add(new Custom() { Name = "Jack", Age = 21, Address = "NewYork" }); customs.Add(new Custom() { Name = "Jime", Age = 26, Address = "China" }); customs.Add(new Custom() { Name = "Tina", Age = 29, Address = "ShangHai" }); customs.Add(new Custom() { Name = "Luo", Age = 30, Address = "Beijing" }); customs.Add(new Custom() { Name = "Wang", Age = 60, Address = "Guangdong" }); customs.Add(new Custom() { Name = "Feng", Age = 25, Address = "YunNan" }); } sw.Start(); var result = customs.Where<Custom>(c => c.Age > 26).ToList(); sw.Stop(); Console.WriteLine("Linq time is {0}.",sw.ElapsedMilliseconds); sw.Restart(); sw.Start(); var result2 = customs.AsParallel().Where<Custom>(c => c.Age > 26).ToList(); sw.Stop(); Console.WriteLine("Parallel Linq time is {0}.", sw.ElapsedMilliseconds); }
其實也就是加了一個AsParallel()方法,下面來看下執行結果:
時間相差了一倍,不過有時候不會相差這麼多,要看系統當前的資源利用率。大家可以多測試一下。
其實,AsParallel()這個方法可以應用與任何集合,包括List<T>集合,從而提高查詢速度和系統效能。
2、GroupBy方法
在專案中,我們經常要對資料做處理,比如分組統計,我們知道在linq中也可以實現,今天來學習一下新的ToLookup方法,寫一個測試方法,程式碼如下:
public static void OrderByTest() { Stopwatch stopWatch = new Stopwatch(); List<Custom> customs = new List<Custom>(); for (int i = 0; i < 2000000; i++) { customs.Add(new Custom() { Name = "Jack", Age = 21, Address = "NewYork" }); customs.Add(new Custom() { Name = "Jime", Age = 26, Address = "China" }); customs.Add(new Custom() { Name = "Tina", Age = 29, Address = "ShangHai" }); customs.Add(new Custom() { Name = "Luo", Age = 30, Address = "Beijing" }); customs.Add(new Custom() { Name = "Wang", Age = 60, Address = "Guangdong" }); customs.Add(new Custom() { Name = "Feng", Age = 25, Address = "YunNan" }); } stopWatch.Restart(); var groupByAge = customs.GroupBy(item => item.Age).ToList(); foreach (var item in groupByAge) { Console.WriteLine("Age={0},count = {1}", item.Key, item.Count()); } stopWatch.Stop(); Console.WriteLine("Linq group by time is: " + stopWatch.ElapsedMilliseconds); stopWatch.Restart(); var lookupList = customs.ToLookup(i => i.Age); foreach (var item in lookupList) { Console.WriteLine("LookUP:Age={0},count = {1}", item.Key, item.Count()); } stopWatch.Stop(); Console.WriteLine("LookUp group by time is: " + stopWatch.ElapsedMilliseconds); }
執行結果如下:
ToLookup方法是將集合轉換成一個只讀集合,所以在大資料量分組時效能優於List.大家可以查閱相關資料,這裡由於篇幅問題,不再細說。