詳解C#異步多線程使用中的常見(jiàn)問(wèn)題_第1頁(yè)
詳解C#異步多線程使用中的常見(jiàn)問(wèn)題_第2頁(yè)
詳解C#異步多線程使用中的常見(jiàn)問(wèn)題_第3頁(yè)
詳解C#異步多線程使用中的常見(jiàn)問(wèn)題_第4頁(yè)
詳解C#異步多線程使用中的常見(jiàn)問(wèn)題_第5頁(yè)
已閱讀5頁(yè),還剩8頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第詳解C#異步多線程使用中的常見(jiàn)問(wèn)題目錄異常處理線程取消臨時(shí)變量線程安全

異常處理

小伙伴有沒(méi)有想過(guò),多線程的異常怎么處理,同步方法內(nèi)的異常處理,想必都非常非常熟悉了。那多線程是什么樣的呢,接著我講解多線程的異常處理

首先,我們定義個(gè)任務(wù)列表,當(dāng)11、12次的時(shí)候,拋出一個(gè)異常,最外圍使用trycatch包一下

staticvoidMain(string[]args)

Console.WriteLine($"MainStart,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

TaskFactorytaskFactory=newTaskFactory();

ListTasktasks=newListTask

for(inti=0;ii++)

stringname=$"第{i}次";

Actionobjectaction=t=

Thread.Sleep(2*1000);

if(name.ToString().Equals("第11次"))

thrownewException($"{t},執(zhí)行失敗");

if(name.ToString().Equals("第12次"))

thrownewException($"{t},執(zhí)行失敗");

Console.WriteLine($"{t},執(zhí)行成功");

tasks.Add(taskFactory.StartNew(action,name));

catch(AggregateExceptionaex)

foreach(variteminaex.InnerExceptions)

Console.WriteLine("MainAggregateException:"+item.Message);

catch(Exceptionex)

Console.WriteLine("MainException:"+ex.Message);

Console.WriteLine($"MainEnd,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

啟動(dòng)程序,可以看到vs捕獲到了異常的代碼行,但catch并未捕獲到異常,這是為什么呢?是因?yàn)榫€程里面的異常被吞掉了,從運(yùn)行的結(jié)果也可以看到,mainend在子線程沒(méi)有執(zhí)行任時(shí)就已經(jīng)結(jié)束了,那說(shuō)明catch已經(jīng)執(zhí)行過(guò)去了。

那有沒(méi)有辦法捕獲多線程的異常呢?答案:有的,等待線程完成計(jì)算即可

看下面代碼,有個(gè)特殊的地方AggregateException.InnerExceptions專(zhuān)門(mén)為多線程準(zhǔn)備的,可以查看多線程異常信息

staticvoidMain(string[]args)

Console.WriteLine($"MainStart,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

TaskFactorytaskFactory=newTaskFactory();

ListTasktasks=newListTask

for(inti=0;ii++)

stringname=$"第{i}次";

Actionobjectaction=t=

Thread.Sleep(2*1000);

if(name.ToString().Equals("第11次"))

thrownewException($"{t},執(zhí)行失敗");

if(name.ToString().Equals("第12次"))

thrownewException($"{t},執(zhí)行失敗");

Console.WriteLine($"{t},執(zhí)行成功");

tasks.Add(taskFactory.StartNew(action,name));

Task.WaitAll(tasks.ToArray());

catch(AggregateExceptionaex)

foreach(variteminaex.InnerExceptions)

Console.WriteLine("MainAggregateException:"+item.Message);

catch(Exceptionex)

Console.WriteLine("MainException:"+ex.Message);

Console.WriteLine($"MainEnd,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

啟動(dòng)線程,可以看到任務(wù)全部執(zhí)行完畢,且AggregateException.InnerExceptions存儲(chǔ)了,子線程執(zhí)行時(shí)的異常信息

但WaitAll不好,總不能一直WaitAll吧,它會(huì)卡界面。并不適用于異步場(chǎng)景對(duì)吧,接著來(lái)看另外一直解決方案。就是子線程里不允許出現(xiàn)異常,如果有自己處理好,即trycatch包一下,平時(shí)工作中建議這么做。

使用trycatch將子線程執(zhí)行的代碼包一下,且在catch打印錯(cuò)誤信息

staticvoidMain(string[]args)

Console.WriteLine($"MainStart,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

TaskFactorytaskFactory=newTaskFactory();

ListTasktasks=newListTask

for(inti=0;ii++)

stringname=$"第{i}次";

Actionobjectaction=t=

Thread.Sleep(2*1000);

if(name.ToString().Equals("第11次"))

thrownewException($"{t},執(zhí)行失敗");

if(name.ToString().Equals("第12次"))

thrownewException($"{t},執(zhí)行失敗");

Console.WriteLine($"{t},執(zhí)行成功");

catch(Exceptionex)

Console.WriteLine(ex.Message);

tasks.Add(taskFactory.StartNew(action,name));

catch(AggregateExceptionaex)

foreach(variteminaex.InnerExceptions)

Console.WriteLine("MainAggregateException:"+item.Message);

catch(Exceptionex)

Console.WriteLine("MainException:"+ex.Message);

Console.WriteLine($"MainEnd,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

啟動(dòng)程序,可以看到任務(wù)全部執(zhí)行,且子線程異常也捕獲到

線程取消

有時(shí)候會(huì)有這樣的場(chǎng)景,多個(gè)任務(wù)并發(fā)執(zhí)行,如果某個(gè)任務(wù)失敗了,通知其他的任務(wù)都停下來(lái)。首先打個(gè)預(yù)防針Task在外部無(wú)法中止的,Thread.Abort不靠譜。其實(shí)線程取消的這個(gè)想法是錯(cuò)誤的,線程是OS的資源,程序是無(wú)法掌控什么時(shí)候取消,發(fā)出一個(gè)動(dòng)作可能立馬取消,也可能等1s取消。

解決方案:線程自己停止自己,定義公共的變量,修改變量狀態(tài),其他線程不斷檢測(cè)公共變量

例如:CancellationTokenSource就是公共變量,初始化為false狀態(tài),程序執(zhí)行CancellationTokenSource.Cancel()方法會(huì)取消,其他線程檢測(cè)到CancellationTokenSource.IsCancellationRequested會(huì)是取消狀態(tài)。CancellationTokenSource.Token在啟動(dòng)Task時(shí)傳入,如果已經(jīng)CancellationTokenSource.Cancel(),這個(gè)任務(wù)會(huì)放棄啟動(dòng),拋出一個(gè)異常的形式放棄。

staticvoidMain(string[]args)

Console.WriteLine($"MainStart,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

TaskFactorytaskFactory=newTaskFactory();

ListTasktasks=newListTask

CancellationTokenSourcecancellationTokenSource=newCancellationTokenSource();//bool

for(inti=0;ii++)

stringname=$"第{i}次";

Actionobjectaction=t=

Thread.Sleep(2*1000);

if(name.ToString().Equals("第11次"))

thrownewException($"{t},執(zhí)行失敗");

if(name.ToString().Equals("第12次"))

thrownewException($"{t},執(zhí)行失敗");

if(cancellationTokenSource.IsCancellationRequested)//檢測(cè)信號(hào)量

Console.WriteLine($"{t},放棄執(zhí)行");

return;

Console.WriteLine($"{t},執(zhí)行成功");

catch(Exceptionex)

cancellationTokenSource.Cancel();

Console.WriteLine(ex.Message);

tasks.Add(taskFactory.StartNew(action,name,cancellationTokenSource.Token));

Task.WaitAll(tasks.ToArray());

catch(AggregateExceptionaex)

foreach(variteminaex.InnerExceptions)

Console.WriteLine("MainAggregateException:"+item.Message);

catch(Exceptionex)

Console.WriteLine("MainException:"+ex.Message);

Console.WriteLine($"MainEnd,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

啟動(dòng)程序,可以看到11、12此任務(wù)失敗,18、19放棄了任務(wù)執(zhí)。有的小伙伴疑問(wèn)了,12之后的部分為什么執(zhí)行成功了,因?yàn)镃PU是分時(shí)分片的嗎,會(huì)有延遲,延遲少不了。

臨時(shí)變量

首先看個(gè)代碼,循環(huán)5次,多線程的方式,依次輸出序號(hào)

staticvoidMain(string[]args)

Console.WriteLine($"MainStart,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

for(inti=0;ii++)

Task.Run(()={

Console.WriteLine(i);

Console.WriteLine($"MainEnd,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

啟動(dòng)程序,不是我們預(yù)期的結(jié)果0、1、2、3、4,為什么是5個(gè)5呢?因?yàn)槿讨挥幸粋€(gè)i,當(dāng)主線程執(zhí)行完畢時(shí)i=5,但子線程可能還沒(méi)有開(kāi)始執(zhí)行任務(wù),輪到子線程取i時(shí),已經(jīng)是主線程1循環(huán)完畢后的5了。

改造代碼:在for循環(huán)內(nèi)加一行代碼intk=i,且在子線程用的變量也改為k

staticvoidMain(string[]args)

Console.WriteLine($"MainStart,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

for(inti=0;ii++)

intk=i;

Task.Run(()={

Console.WriteLine($"k={k},i={i}");

Console.WriteLine($"MainEnd,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

啟動(dòng)程序,可以看到是我們預(yù)期的結(jié)果0、1、2、3、4,為什么會(huì)這樣子呢?因?yàn)槿逃?個(gè)k,每次循環(huán)都會(huì)創(chuàng)建一個(gè)k存儲(chǔ)當(dāng)前的i,不同的子線程使用的也是,每次循環(huán)的i值。

線程安全

首先為什么會(huì)有線程安全的概念呢?首先我們來(lái)看一個(gè)正常程序,如下

staticvoidMain(string[]args)

Console.WriteLine($"MainStart,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

intTotalCount=0;

Listintvs=newListint

for(inti=0;i10000;i++)

TotalCount+=1;

vs.Add(i);

Console.WriteLine(TotalCount);

Console.WriteLine(vs.Count);

Console.WriteLine($"MainEnd,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

啟動(dòng)程序,可以看到循環(huán)10000次,最終的求和與列表里的數(shù)據(jù)量都是10000,這是正常的

接著,將求和與添加列表,換成多線程,等待全部線程完成工作后,打印信息

staticvoidMain(string[]args)

Console.WriteLine($"MainStart,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

intTotalCount=0;

Listintvs=newListint

TaskFactorytaskFactory=newTaskFactory();

ListTasktasks=newListTask

for(inti=0;i10000;i++)

intk=i;

tasks.Add(taskFactory.StartNew(()=

TotalCount+=1;

vs.Add(i);

}));

Task.WaitAll(tasks.ToArray());

Console.WriteLine(TotalCount);

Console.WriteLine(vs.Count);

Console.WriteLine($"MainEnd,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

啟動(dòng)程序,可以看到,兩個(gè)結(jié)果都不是10000呢?這就是線程安全

因?yàn)門(mén)otalCount是個(gè)共享的變量,當(dāng)多個(gè)線程去取TotalCount進(jìn)行+1后,線程都去放值的時(shí)候,后一個(gè)線程會(huì)替換掉前一個(gè)線程放置的值,所以就會(huì)形成做最終不是10000的結(jié)果。列表,可以看做是一個(gè)連續(xù)的塊,當(dāng)多線程添加的時(shí)候,也會(huì)進(jìn)行覆蓋。

如何解決呢?答案:lock、安全隊(duì)列、拆分合并計(jì)算。下面對(duì)lock進(jìn)行講解,安全隊(duì)列與拆分合并計(jì)算,有興趣的小伙伴可以私下交流

1.lock

第一種,通過(guò)加鎖的方式,這種也是日常工作總常用的一種。首先定義個(gè)私有的靜態(tài)引用類(lèi)型的變量,然后將需要鎖的運(yùn)算放到lock()方法內(nèi)

在{}內(nèi)同一時(shí)刻,只有一個(gè)線程執(zhí)行,所以盡可能{}放置必要的邏輯運(yùn)行提高效率。lock只能鎖引用類(lèi)型,原理是占用這個(gè)引用鏈接。不要用string會(huì)享元,即如lock()是相同的字符串,無(wú)論定義多少個(gè)變量,其實(shí)都是一個(gè)。

internalclassProgram

privatestaticreadonlyobject_lock=newobject();

staticvoidMain(string[]args)

Console.WriteLine($"MainStart,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

intTotalCount=0;

Listintvs=newListint

TaskFactorytaskFactory=newTaskFactory();

ListTasktasks=newListTask

for(inti=0;i10000;i++)

intk=i;

tasks.Add(taskFactory.StartNew(()=

lock(_lock)

TotalCount+=1;

vs.Add(i);

}));

Task.WaitAll(tasks.ToArray());

Console.WriteLine(TotalCount);

Console.WriteLine(vs.Count);

Console.WriteLine($"MainEnd,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論