13异步多线程(三)Parallel,线程安全
一.Parallel
和Task很像,啟動多個線程計算,但是主線程也參與計算,所以它也會卡界面。它本質上相當于Task+WaitAll,只要用到了Task開啟多個線程并且要WaitAll,就可以用Parallel,還可以節約一個線程(主線程參與運算)。
代碼如下:
Parallel.Invoke(() => this.DoSomethingLong("btnParallel_Click_002"),() => this.DoSomethingLong("btnParallel_Click_001"),() => this.DoSomethingLong("btnParallel_Click_003"),() => this.DoSomethingLong("btnParallel_Click_004"),() => this.DoSomethingLong("btnParallel_Click_005"));返回結果如下:
Parallel還有一些API,比如:
Parallel.For(0, 5, t => {this.DoSomethingLong($"btnParallel_Click_00{t}"); });這段代碼借助了for循環,和上面代碼寫的是一樣的。還可以這樣寫:
Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, t => {this.DoSomethingLong($"btnParallel_Click_00{t}"); });這里有個問題,如果要循環100次請求,有100個線程給我們用嗎?當然不會的,這時候要設定一個開啟線程并發的最大值,開啟的線程只有執行完了,下一個線程才可以執行,可以這樣寫:
ParallelOptions options = new ParallelOptions() {MaxDegreeOfParallelism = 3 //最大3個線程并發任務 };Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, options, t => {this.DoSomethingLong($"btnParallel_Click_00{t}"); });執行結果如下:
?
如果在執行過程中還需要人工干預的話,可以加上state,并對state進行stop,break操作,比如下面:
Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, options, (t, state) =>{this.DoSomethingLong($"btnParallel_Click_00{t}");//state.Stop();//結束全部的//state.Break();//停止當前的//return;});?
二.多線程的異常處理
?
private void btnThreadCore_Click(object sender, EventArgs e){try{TaskFactory taskFactory = new TaskFactory();List<Task> taskList = new List<Task>();for (int i = 0; i < 20; i++){string name = string.Format($"btnThreadCore_Click_{i}");Action<object> act = t =>{//多線程內部要加try catch,處理自己的異常try{Thread.Sleep(2000);//遇上11,則會拋出異常,顯示“執行失敗”if (t.ToString().Equals("btnThreadCore_Click_11")){throw new Exception(string.Format($"{t} 執行失敗"));}//遇上12,則會拋出異常,顯示“執行失敗”if (t.ToString().Equals("btnThreadCore_Click_12")){throw new Exception(string.Format($"{t} 執行失敗"));}Console.WriteLine("{0} 執行成功", t);}catch (Exception ex){Console.WriteLine(ex.Message);}};taskList.Add(taskFactory.StartNew(act, name));}}catch (AggregateException aex){foreach (var item in aex.InnerExceptions){Console.WriteLine(item.Message);}}catch (Exception ex){Console.WriteLine(ex.Message);}}顯示結果:
?
好了,如果我們內部不抓取catch,放到外面去抓catch呢?代碼這樣的:
private void btnThreadCore_Click(object sender, EventArgs e){try{TaskFactory taskFactory = new TaskFactory();List<Task> taskList = new List<Task>();for (int i = 0; i < 20; i++){string name = string.Format($"btnThreadCore_Click_{i}");Action<object> act = t =>{Thread.Sleep(2000);if (t.ToString().Equals("btnThreadCore_Click_11")){throw new Exception(string.Format($"{t} 執行失敗"));}if (t.ToString().Equals("btnThreadCore_Click_12")){throw new Exception(string.Format($"{t} 執行失敗"));}Console.WriteLine("{0} 執行成功", t);};taskList.Add(taskFactory.StartNew(act, name));}}catch (AggregateException aex){foreach (var item in aex.InnerExceptions){Console.WriteLine(item.Message);}}catch (Exception ex){Console.WriteLine(ex.Message);}}這樣是很危險的,異常不拋出,外面獲取不了,還以為全部執行成功了。
外面能不能獲取到異常呢?可以的,代碼如下:
private void btnThreadCore_Click(object sender, EventArgs e){try{TaskFactory taskFactory = new TaskFactory();List<Task> taskList = new List<Task>();for (int i = 0; i < 20; i++){string name = string.Format($"btnThreadCore_Click_{i}");Action<object> act = t =>{try{Thread.Sleep(2000);if (t.ToString().Equals("btnThreadCore_Click_11")){throw new Exception(string.Format($"{t} 執行失敗"));}if (t.ToString().Equals("btnThreadCore_Click_12")){throw new Exception(string.Format($"{t} 執行失敗"));}Console.WriteLine("{0} 執行成功", t);}catch (Exception ex){Console.WriteLine(ex.Message);}};taskList.Add(taskFactory.StartNew(act, name));}//就加這一句,意思是等待所有線程全部執行完Task.WaitAll(taskList.ToArray());}catch (AggregateException aex){foreach (var item in aex.InnerExceptions){Console.WriteLine(item.Message);}}catch (Exception ex){Console.WriteLine(ex.Message);}}這樣就可以在委托外面獲取到異常了:
不過,還是不要這么做,最好用第一種方法,委托內部try catch,獲取異常,因為不知道外面有沒有
taskList.Add(taskFactory.StartNew(act, name));? ?這個語句。
三.取消線程
場景:多個線程并發,如果一個任務失敗就要求其他任務不再執行了。
線程取消不是操作線程,而是操作信號量(共享變量,多個線程都能訪問到的東西,變量/數據庫的數據/硬盤數據)。
每個線程在執行的過程中,經常去查看下這個信號量,然后自己結束自己,線程不能別人終止,只能自己干掉自己,延遲是少不了的。
CancellationTokenSource可以在cancel后,取消沒有啟動的任務
看一下執行結果:
?
四.多線程的臨時變量
再看一個,每個循環中定義一個K,并且等于i
五.線程安全
循環10000次,TotalCount每次加1,IntList集合每次新增一個元素,那么按照原計劃,打印出來,TotalCount=10000,IntLis.Count = 10000。運行后看結果:
不是預想的10000。因為是多線程,可能變量被幾個線程同時在修改,所以就少了,變量應該放在多線程內部,不要共享出去,這樣才是安全的。
解決多線程沖突第一個辦法:lock,lock的方法塊兒里面是單線程的;lock里面的代碼要盡量的少
先看lock:
lock是語法糖,?lock==Monitor.Enter,檢查下這個變量有沒有被lock? ?有就等著,沒有就占有,然后進去執行,執行完了釋放。
看一下怎樣用lock改造上面的代碼:
先定義一個讓lock鎖定的變量,是個靜態的
結果:
這樣就不會少了,lock對btnThreadCore_Click_Lock這個變量(變量必須是引用類型)進行鎖定,其他線程如果進來,發現btnThreadCore_Click_Lock被鎖定了,只有等待釋放,所以?lock塊中每次只允許進入一個線程,lock塊變成了單線程,既然lock塊變成了單線程,那么里面的代碼越少越好,早點執行完。
有兩個錯誤的使用lock的方法:
1.lock(this)?
鎖定了當前實例,別的地方如果要使用這個變量呢?都被鎖定了 ,如果每個實例想要單獨的鎖定 ,用 private object
2.鎖定一個字符串
比如?string a="123456"; lock(a)
a="123456",已經被內存中分配空間了,如果下次定義了b也等于"123456"(享元模式的內存分配,字符串是唯一的),string是引用類型,系統會把a和b當作同一個內存地址,而a已經被鎖定,相當于b也被鎖定了,如果此時有線程想鎖定b,就只能等待。
解決多線程沖突第二個辦法:沒有沖突,從數據上隔離開,即線程之間不要有共享數據,每個線程之間不要數據交叉,各自處理自己的數據,這樣就避免了沖突。
總結
以上是生活随笔為你收集整理的13异步多线程(三)Parallel,线程安全的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 10003 微信登录失败 redirec
- 下一篇: katana工程搭建以及模型相机的导入