天天看點

.net研究院之多線程技術6-異常處理-線程間通知-臨時變量-線程安全&Lock

多線程的異常處理示例

線程異常後經常是需要通知别的線程,而不是等到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.資料分拆,避免多線程操作同一個資料;又安全又高效,這才是最頂的