一、Task(任務)和ThreadPool(執行緒池)不同
原始碼
1、執行緒(Thread)是創建并發工具的底層類,但是在前幾篇文章中我們介紹了Thread的特點,和實體,可以很明顯發現局限性(回傳值不好獲取(必須在一個作用域中)),當我們執行緒執行完之后不能很好的進行下一次任務的執行,需要多次銷毀和創建,所以不是很容易使用在多并發的情況下,
2、執行緒池(ThreadPool) QueueUserWorkItem是很容易發起并發任務,也解決了上面我們的需要多次創建、銷毀的性能損耗解決了,但是我們就是太簡單的,我不知道執行緒什么時候結束,也沒有獲取回傳值的途徑,也是比較尷尬的事情,
3、任務(Task)表示一個通過或不通過執行緒實作的并發操作,任務是可組合的,使用延續(continuation)可將它們串聯在一起,它們可以使用執行緒池減少啟動延遲,可使用回呼方法避免多個執行緒同時等待I/O密集操作,
二、初識Task(任務)
1、Task(任務)是在.NET 4.0引入的、Task是在我們執行緒池ThreadPool上面進行進一步的優化,所以Task默認還是執行緒池執行緒,并且是后臺執行緒,當我們的主執行緒結束時其他執行緒也會結束
2、Task創建任務,也和之前差不多
/// <summary> /// Task 的使用 /// Task 的創建還是差不多的 /// </summary> public static void Show() { //實體方式 Task task = new Task(() => { Console.WriteLine("無回傳引數的委托"); }); //無參有回傳值 Task<string> task1 = new Task<string>(() => { return "我是回傳值"; }); //有參有回傳值 Task<string> task2 = new Task<string>(x => { return "回傳值 -- " + x.ToString(); }, "我是輸入引數"); //開啟執行緒 task2.Start(); //獲取回傳值 Result會堵塞執行緒獲取回傳值 Console.WriteLine(task2.Result); //使用執行緒工廠創建 無引數無回傳值執行緒 Task.Factory.StartNew(() => { Console.WriteLine("這個是執行緒工廠創建"); }).Start(); //使用執行緒工廠創建 有引數有回傳值執行緒 Task.Factory.StartNew(x => { return "回傳值 -- " + x.ToString(); ; }, "我是引數"); //直接靜態方法運行 Task.Run(() => { Console.WriteLine("無回傳引數的委托"); }); }View Code

說明:
1、事實上Task.Factory型別本身就是TaskFactory(任務工廠),而Task.Run(在.NET4.5引入,4.0版本呼叫的是后者)是Task.Factory.StartNew的簡寫法,是后者的多載版本,更靈活簡單些,
2、呼叫靜態Run方法會自動創建Task物件并立即呼叫Start
3、Task.Run等方式啟動任務并沒有呼叫Start,因為它創建的是“熱”任務,相反“冷”任務的創建是通過Task建構式,
三、Task(任務進階)
1、Wait 等待Task執行緒完成才會執行后續動作
//創建一個執行緒使用Wait堵塞執行緒 Task.Run(() => { Console.WriteLine("Wait 等待Task執行緒完成才會執行后續動作"); }).Wait();View Code
2、WaitAll 等待Task[] 執行緒陣列全部執行成功之后才會執行后續動作
//創建一個裝載執行緒的容器 List<Task> list = new List<Task>(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WaitAll 執行"); })); } Task.WaitAll(list.ToArray()); Console.WriteLine("Wait執行完畢");View Code
3、WaitAny 等待Task[] 執行緒陣列任一執行成功之后就會執行后續動作
//創建一個裝載執行緒的容器 List<Task> list = new List<Task>(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WaitAny 執行"); })); } Task.WaitAny(list.ToArray()); Console.WriteLine("WaitAny 執行完畢");View Code
4、WhenAll 等待Task[] 執行緒陣列全部執行成功之后才會執行后續動作、與WaitAll不同的是他有回呼函式ContinueWith
//創建一個裝載執行緒的容器 List<Task> list = new List<Task>(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WhenAll 執行"); })); } Task.WhenAll(list.ToArray()).ContinueWith(x => { return x.AsyncState; }); Console.WriteLine("WhenAll 執行完畢");View Code
5、WhenAny 等待Task[] 執行緒陣列任一執行成功之后就會執行后續動作、與WaitAny不同的是他有回呼函式ContinueWith
//創建一個裝載執行緒的容器 List<Task> list = new List<Task>(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WhenAny 執行"); })); } Task.WhenAny(list.ToArray()).ContinueWith(x => { return x.AsyncState; }); Console.WriteLine("WhenAny 執行完畢"); Console.ReadLine();View Code
四、Parallel 并發控制
1、是在Task的基礎上做了封裝 4.5,使用起來比較簡單,如果我們執行100個任務,只能用到10個執行緒我們就可以使用Parallel并發控制
public static void Show5() { //第一種方法是 Parallel.Invoke(() => { Console.WriteLine("我是執行緒一號"); }, () => { Console.WriteLine("我是執行緒二號"); }, () => { Console.WriteLine("我是執行緒三號"); }); //for 方式創建多執行緒 Parallel.For(0, 5, x => { Console.WriteLine("這個看名字就知道是for了哈哈 i=" + x); }); //ForEach 方式創建多執行緒 Parallel.ForEach(new string[] { "0", "1", "2", "3", "4" }, x => Console.WriteLine("這個看名字就知道是ForEach了哈哈 i=" + x)); //這個我們包一層,就不會卡主界面了 Task.Run(() => { //創建執行緒選項 ParallelOptions parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = 3 }; //創建一個并發執行緒 Parallel.For(0, 5, parallelOptions, x => { Console.WriteLine("限制執行的次數"); }); }).Wait(); Console.WriteLine("**************************************"); //Break Stop 都不推薦用 ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 3; Parallel.For(0, 40, parallelOptions, (i, state) => { if (i == 20) { Console.WriteLine("執行緒Break,Parallel結束"); state.Break();//結束Parallel //return;//必須帶上 } if (i == 2) { Console.WriteLine("執行緒Stop,當前任務結束"); state.Stop();//當前這次結束 //return;//必須帶上 } Console.WriteLine("我是執行緒i=" + i); }); }View Code
五、多執行緒實體
1、代碼例外我資訊大家都不陌生,比如我剛剛寫代碼經常會報 =>物件未定義null 的真的是讓我心痛了一地,那我們的多執行緒中怎么去處理代碼例外呢? 和我們經常寫的同步方法不一樣,同步方法遇到錯誤會直接拋出,當是如果我們的多執行緒中出現代碼例外,那么這個例外會自動傳遞呼叫Wait 或者 Task<TResult> 的Result屬性上面,任務的例外會將自動捕獲并且拋給呼叫者,為了確保報告所有的例外,CLR會將例外封裝到AggregateExcepiton容器中,這容器是公開了InnerExceptions屬性中包含所有捕獲的例外,但是如果我們的執行緒沒有等待結束不會獲取到例外,
class Program { static void Main(string[] args) { try { Task.Run(() => { throw new Exception("錯誤"); }).Wait(); } catch (AggregateException axe) { foreach (var item in axe.InnerExceptions) { Console.WriteLine(item.Message); } } Console.ReadKey(); } }View Code
/// <summary> /// 多執行緒捕獲例外 /// 多執行緒會將我們的例外吞了,因為我們的執行緒執行會直接執行完代碼,不會去等待你捕獲到我的例外, /// 我們的執行緒中最好是不要出現例外,自己處理好, /// </summary> public static void Show() { //創建一個多執行緒工廠 TaskFactory taskFactory = new TaskFactory(); //創建一個多執行緒容器 List<Task> tasks = new List<Task>(); //創建委托 Action action = () => { try { string str = "sad"; int num = int.Parse(str); } catch (AggregateException ax) { Console.WriteLine("我是AggregateException 我抓到了例外啦 ax:" + ax); } catch (Exception) { Console.WriteLine("我是執行緒我已經報錯了"); } }; //這個是我們經常需要做的捕獲例外 try { //創建10個多執行緒 for (int i = 0; i < 10; i++) { tasks.Add(taskFactory.StartNew(action)); } Task.WaitAll(tasks.ToArray()); } catch (Exception ex) { Console.WriteLine("例外啦"); } Console.WriteLine("我已經執行完了"); }View Code
2、多執行緒取消機制,我們的Task在外部無法進行暫停 Thread().Abort() 無法很好控制,上上篇中Thread我們也講到了Thread().Abort() 的不足之處,有問題就有解決方案,如果我們使用一個全域的變數控制,就需要不斷的監控我們的變數取消執行緒,那么說當然有對應的方法啦,CancellationTokenSource (取消標記源)我們可以創建一個取消標記源,我們在創建執行緒的時候傳入我們取消標記源Token,Cancel()方法 取消執行緒,IsCancellationRequested 回傳一個bool值,判斷是不是取消了執行緒了,
/// <summary> /// 多執行緒取消機制 我們的Task在外部無法進行暫停 Thread().Abort() 無法很好控制,我們的執行緒, /// 如果我們使用一個全域的變數控制,就需要不斷的監控我們的變數取消執行緒, /// 我們可以創建一個取消標記源,我們在創建執行緒的時候傳入我們取消標記源Token /// Cancel() 取消執行緒,IsCancellationRequested 回傳一個bool值,判斷是不是取消了執行緒了 /// </summary> public static void Show1() { //創建一個取消標記源 CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); //創建一個多執行緒工廠 TaskFactory taskFactory = new TaskFactory(); //創建一個多執行緒容器 List<Task> tasks = new List<Task>(); //創建委托 Action<object> action = x => { try { //每個執行緒我等待2秒鐘,不然 Thread.Sleep(2000); //判斷是不是取消執行緒了 if (cancellationTokenSource.IsCancellationRequested) { Console.WriteLine("放棄執行后面執行緒"); return; } if (Convert.ToUInt32(x) == 20) { throw new Exception(string.Format("{0} 執行失敗", x)); } Console.WriteLine("我是正常的我在執行"); } catch (AggregateException ax) { Console.WriteLine("我是AggregateException 我抓到了例外啦 ax:" + ax); } catch (Exception ex) { //例外出現取消后面執行的所有執行緒 cancellationTokenSource.Cancel(); Console.WriteLine("我是執行緒我已經報錯了"); } }; //這個是我們經常需要做的捕獲例外 try { //創建10個多執行緒 for (int i = 0; i < 50; i++) { int k = i; tasks.Add(taskFactory.StartNew(action, k, cancellationTokenSource.Token)); } Task.WaitAll(tasks.ToArray()); } catch (Exception ex) { Console.WriteLine("例外啦"); } Console.WriteLine("我已經執行完了"); }View Code

3、多執行緒創建臨時變數,當我們啟動執行緒之后他們執行沒有先后快慢之分,正常的回圈中的變數也沒有作用,這個時候就要創建一個臨時變數存盤資訊,解決不訪問一個資料源,
/// <summary> /// 執行緒臨時變數 /// </summary> public static void Show2() { //創建一個執行緒工廠 TaskFactory taskFactory = new TaskFactory(); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); //創建一個委托 Action<object> action = x => { Console.WriteLine("傳入引數 x:" + x); }; for (int i = 0; i < 20; i++) { //這最主要的就是會創建20個k的臨時變數 int k = i; taskFactory.StartNew(action, k); } Console.ReadLine(); }View Code

4、多執行緒鎖,之前我們有提到過我們的多執行緒可以同時公共資源,如果我們有個變數需要加一,但是和這個時候我們有10個執行緒同時操作這個會怎么樣呢?
public static List<int> list = new List<int>(); public static int count = 0; public static void Show3() { //創建執行緒容器 List<Task> tasks = new List<Task>(); for (int i = 0; i < 10000; i++) { //添加執行緒 tasks.Add(Task.Run(() => { list.Add(i); count++; })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine("list 行數:" + list.Count + " count 總數:" + count); Console.ReadLine(); }
我們上面的代碼本來是count++到10000,但是我們看到結果的時候,我們是不是傻了呀,怎么是不是說好的10000呢,其實的資料讓狗吃了?真的是小朋友有很多問號??????

5、那么我們要怎么去解決這個問題呢?方法還是有的今天我們要將到一個語法糖lock、它能做什么呢?它相當于一個代碼塊鎖,它主要鎖的是一個物件,當它鎖住物件的時候會當其他執行緒發生堵塞,因為當它鎖住代碼時候也是鎖住了物件的訪問鏈,是其他的執行緒不能訪問,必須等待物件訪問鏈被釋放之后才能被一個執行緒訪問,我們的使用lock鎖代碼塊的時候,盡量減少鎖入代碼塊范圍,因為我們鎖代碼之后會導致只有一個執行緒可以拿到資料,盡量只要必須使用lock的地方使用,
6、Lock使用要注意的地方
1、lock只能鎖參考型別的物件.
2、不能鎖空物件null某一物件可以指向Null,但Null是不需要被釋放的,(請參考:認識全面的null),
3、lock 盡量不要去鎖string 型別雖然它是參考型別,但是string是享元模式,字串型別被CLR“暫留”
這意味著整個程式中任何給定字串都只有一個實體,就是這同一個物件表示了所有運行的應用程式域的所有執行緒中的該文本,因此,只要在應用程式行程中的任何位置處具有相同內容的字串上放置了鎖,就將鎖定應用程式中該字串的所有實體,因此,最好鎖定不會被暫留的私有或受保護成員,
4、lock就避免鎖定public 型別或不受程式控制的物件,例如,如果該實體可以被公開訪問,則 lock(this) 可能會有問題,因為不受控制的代碼也可能會鎖定該物件,這可能導致死鎖,即兩個或更多個執行緒等待釋放同一物件,出于同樣的原因,鎖定公共資料型別(相比于物件)也可能導致問題,
/// <summary> /// 創建一個靜態物件,主要是用于鎖代碼塊,如果是靜態的就會全域鎖,如果要鎖實體類,就不使用靜態就好了 /// </summary> private readonly static object obj = new object(); public static List<int> list = new List<int>(); public static int count = 0; /// <summary> /// lock 多執行緒鎖 /// 當我們的執行緒訪問同一個全域變數、同時訪問同一個區域變數、同一個檔案夾,就會出現執行緒不安全 /// 我們的使用lock鎖代碼塊的時候,盡量減少鎖入代碼塊范圍,因為我們鎖代碼之后會導致只有一個執行緒可以 /// 訪問到我們代碼塊了 /// </summary> public static void Show3() { //創建執行緒容器 List<Task> tasks = new List<Task>(); //鎖代碼 for (int i = 0; i < 10000; i++) { //添加執行緒 tasks.Add(Task.Run(() => { //鎖代碼 lock (obj) { //這個里面就只會出現一個執行緒訪問,資源, list.Add(i); count++; } //lock 是一個語法糖,就是下面的代碼 Monitor.Enter(obj); Monitor.Exit(obj); })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine("list 行數:" + list.Count + " count 總數:" + count); Console.ReadLine(); }
7、總結實體篇,雙色球實體,
1、雙色球:投注號碼由6個紅色球號碼和1個藍色球號碼組成,紅色球號碼從01--33中選擇(不重復)藍色球號碼從01--16中選擇(可以跟紅球重復),代碼我已經實作了大家可以下載原始碼,只有自己多多倒騰才能讓自己的技術成長, 下一次我們async和await這兩個關鍵字下篇記錄

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/52815.html
標籤:C#
