




版權(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 湖南女子學(xué)院《測(cè)試技術(shù)與傳感器》2023-2024學(xué)年第二學(xué)期期末試卷
- 濮陽(yáng)科技職業(yè)學(xué)院《工程經(jīng)濟(jì)與建設(shè)項(xiàng)目管理》2023-2024學(xué)年第二學(xué)期期末試卷
- 吉利學(xué)院《制藥過(guò)程自動(dòng)化技術(shù)實(shí)驗(yàn)》2023-2024學(xué)年第二學(xué)期期末試卷
- 大連汽車(chē)職業(yè)技術(shù)學(xué)院《媒介綜合設(shè)計(jì)》2023-2024學(xué)年第二學(xué)期期末試卷
- 蘭考三農(nóng)職業(yè)學(xué)院《急危重癥護(hù)理學(xué)實(shí)訓(xùn)》2023-2024學(xué)年第二學(xué)期期末試卷
- 賓館客房促銷(xiāo)活動(dòng)方案
- 智能機(jī)械手機(jī)構(gòu)設(shè)計(jì)答辯
- 休閑體育概論課件
- 2025年農(nóng)村山林地轉(zhuǎn)讓合同書(shū)樣本
- 魅族基于互聯(lián)網(wǎng)的營(yíng)銷(xiāo)策劃方案
- 《醫(yī)學(xué)影像診斷學(xué)》分章節(jié)試題庫(kù)含答案大全
- 潛孔鉆機(jī)的教案
- 品牌設(shè)計(jì)的法則
- 老年口腔醫(yī)學(xué) 課件 老年口腔疾病流行病學(xué)、增齡變化
- 鍋爐試題與答案
- 系統(tǒng)解剖學(xué)-肝臟、胰腺
- 2023年第四屆北京市大學(xué)生模擬法庭競(jìng)賽第一輪賽題A
- GB/T 5237.1-2017鋁合金建筑型材第1部分:基材
- GB/T 33289-2016館藏磚石文物保護(hù)修復(fù)記錄規(guī)范
- GB/T 20721-2006自動(dòng)導(dǎo)引車(chē)通用技術(shù)條件
- GB/T 15256-2014硫化橡膠或熱塑性橡膠低溫脆性的測(cè)定(多試樣法)
評(píng)論
0/150
提交評(píng)論