多線程的異常處理示例
線程異常後經常是需要通知别的線程,而不是等到WaitAll,問題就是要線程取消
工作中正常建議:多線程的委托裡面不允許異常,包一層try-catch,然後記錄下來異常資訊,完成需要的操作
如果某一個線程異常了,需要通知或終止其他線程示例
//多線程并發任務,某個失敗後,希望通知别的線程,都停下來,how?
//Thread.Abort--終止線程;向目前線程抛一個異常然後終結任務;線程屬于OS資源,可能不會立即停下來
//Task不能外部終止任務,隻能自己終止自己(上帝才能打敗自己)
//cts有個bool屬性IsCancellationRequested 初始化是false
//調用Cancel方法後變成true(不能再變回去),可以重複cancel
try
{
CancellationTokenSource cts = new CancellationTokenSource();
List<Task> taskList = new List<Task>();
for (int i = 0; i < 50; i++)
{
string name = $"btnThreadCore_Click_{i}";
taskList.Add(Task.Run(() =>
{
try
{
//判斷cts.IsCancellationRequested,如果其他線程已經出現異常了,則終止線程内的任務
if (!cts.IsCancellationRequested)
Console.WriteLine($"This is {name} 開始 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
Thread.Sleep(new Random().Next(50, 100));
if (name.Equals("btnThreadCore_Click_11"))
{
throw new Exception("btnThreadCore_Click_11異常");
}
else if (name.Equals("btnThreadCore_Click_12"))
{
throw new Exception("btnThreadCore_Click_12異常");
}
else if (name.Equals("btnThreadCore_Click_13"))
{
cts.Cancel();
}
//可以在多個核心位置進行判斷
if (!cts.IsCancellationRequested)
{
Console.WriteLine($"This is {name}成功結束 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
else
{
Console.WriteLine($"This is {name}中途停止 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
return;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
//捕獲到其中一個線程異常,則調用cts.Cancel()
cts.Cancel();
}
//也可以在Task.run的參數裡傳遞cts.Token,當出現異常後即不再啟動(建立)後續的線程
}, cts.Token));
}
//1 準備cts 2 try-catch-cancel 3 Action要随時判斷IsCancellationRequested
//盡快停止,肯定有延遲,在判斷環節才會結束
Task.WaitAll(taskList.ToArray());
//如果線程還沒啟動,能不能就别啟動了?
//1 啟動線程傳遞Token 2 異常抓取
//在Cancel時還沒有啟動的任務,就不啟動了;也是抛異常,cts.Token.ThrowIfCancellationRequested
}
//AggregateException是專門捕獲線程異常的
catch (AggregateException aex)
{
foreach (var exception in aex.InnerExceptions)
{
Console.WriteLine(exception.Message);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
臨時變量
#region 臨時變量
{
//for (int i = 0; i < 5; i++)
//{
// Task.Run(() =>
// {
// 最終i顯示都是5
// Console.WriteLine($"This is btnThreadCore_Click_{i} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
// });
//}
//處理方法
//臨時變量問題,線程是非阻塞的,延遲啟動的;線程執行的時候,i已經是5了
//k是閉包裡面的變量,每次循環都有一個獨立的k
//5個k變量 1個i變量
for (int i = 0; i < 5; i++)
{
int k = i;
Task.Run(() =>
{
Console.WriteLine($"This is btnThreadCore_Click_{i}_{k} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
}
}
#endregion
Lock
Lock鎖是排斥其他線程!!
推薦使用的lock鎖變量
private static readonly object Form_Lock = new object();
線程安全:如果你的代碼在程序中有多個線程同時運作這一段,如果每次運作的結果都跟單線程運作時的結果一緻,那麼就是線程安全的
線程安全問題一般都是有全局變量/共享變量/靜态變量/硬碟檔案/資料庫的值,隻要多線程都能通路和修改
發生是因為多個線程相同操作,出現了覆寫,怎麼解決?
1 Lock解決多線程沖突
Lock是文法糖,Monitor.Enter,占據一個引用,别的線程就隻能等着
推薦鎖是private static readonly object,
A不能是Null,可以編譯不能運作;
B 不推薦lock(this),外面如果也要用執行個體,就沖突了
C 不應該是string; string在記憶體配置設定上是重用的,會沖突
D Lock裡面的代碼不要太多,這裡是單線程的
線程安全問題
1.使用官方的線程安全集合,在改集合内進行多線程操作
// 線程安全集合
System.Collections.Concurrent.ConcurrentQueue<int>
2.資料分拆,避免多線程操作同一個資料;又安全又高效,這才是最頂的