1. 程式人生 > >20181106_執行緒之異常_取消_變數_安全Lock

20181106_執行緒之異常_取消_變數_安全Lock

一. 執行緒的異常處理:

 try
            {
                TaskFactory taskFactory = new TaskFactory();
                List<Task> taskList = new List<Task>();
                 
                //執行緒裡面的異常是被吞掉了,因為已經脫離try catch的範圍了  如果真想讓執行緒之外的Catch抓到異常, 那麼只有使用 WaitAll 抓到多執行緒裡面全部的異常
                //實際開發: 執行緒裡面的action不允許出現異常,自己使用try catch處理好
                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($"Exception:{ex.Message}"); //
                        }
                    };
                    taskList.Add(taskFactory.StartNew(act, name));
                }
               // Task.WaitAll(taskList.ToArray());使用 WaitAll 捕獲多執行緒裡面全部的異常; 但是也不能總是WaitAll, 並且很多業務也不能使用WaitAll來處理
                
            }
            catch (AggregateException aex) //專門處理多執行緒的異常, 裡面的異常有多項
            {
                foreach (var item in aex.InnerExceptions)
                {
                    Console.WriteLine(item.Message);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

 

二. 執行緒的取消, 

a)   場景: 多個執行緒併發,某個失敗後,希望通知別的執行緒,都停下來, 儘量使用CancellationTokenSource, 不要自己建立bool變數

 try
            {
                TaskFactory taskFactory = new TaskFactory();
                List<Task> taskList = new List<Task>();

          
                 //多個執行緒併發,某個失敗後,希望通知別的執行緒,都停下來
                 //task是外部無法中止(中間停止),Thread.Abort不靠譜,因為執行緒是OS的資源,無法掌控啥時候取消
                 //執行緒的停止: 執行緒自己停止自己--公共的訪問變數--修改它---執行緒不斷的檢測它(延遲少不了)
                 //CancellationTokenSource去標誌任務是否取消  Cancel取消   IsCancellationRequested  是否已經取消了
                 //Token 啟動Task的時候傳入,那麼如果Cancel了,這個任務會放棄啟動,丟擲一個異常
                 // 當一個執行緒不執行, 後面的執行緒全部都不能再執行了
                 CancellationTokenSource cts = new CancellationTokenSource();//bool值 //bool flag = true; 執行緒安全的
                for (int i = 0; i < 40; i++)
                {
                    string name = string.Format("btnThreadCore_Click{0}", i);
                    Action<object> act = t =>
                    {
                        try
                        {
                            //if (cts.IsCancellationRequested)
                            //{
                            //    Console.WriteLine("{0} 取消一個任務的執行", t);
                            //}
                            Thread.Sleep(2000);
                            if (t.ToString().Equals("btnThreadCore_Click11"))
                            {
                                throw new Exception(string.Format("{0} 執行失敗", t));
                            }
                            if (t.ToString().Equals("btnThreadCore_Click12"))
                            {
                                throw new Exception(string.Format("{0} 執行失敗", t));
                            }
                            if (cts.IsCancellationRequested)//啟動40個執行緒, 每一個執行緒都檢查這個訊號量, 是否改任務已經被取消
                            {
                                Console.WriteLine("{0} 放棄執行", t);
                                return;
                            }
                            else
                            {
                                Console.WriteLine("{0} 執行成功", t);
                            }
                        }
                        catch (Exception ex)
                        {
                            cts.Cancel(); //當執行丟擲異常後, (t.ToString().Equals("btnThreadCore_Click11")); 將訊號量設定為false, 表示取消任務
                            Console.WriteLine(ex.Message);
                        }
                    };
                    taskList.Add(taskFactory.StartNew(act, name, cts.Token));
                    //如果一個執行緒在啟動的時候標識了cts.Token, 當cts.Cancel()時, 如果這個執行緒還沒有啟動, 則會被標識為取消一個任務執行; 如果這個執行緒已經啟動, 但是還沒有執行, 那麼會被放棄執行 
                }
                Task.WaitAll(taskList.ToArray());
                 
            }
            catch (AggregateException aex) //專門處理多執行緒的異常
            {
                foreach (var item in aex.InnerExceptions)
                {
                    Console.WriteLine(item.Message);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

 

三. 多執行緒臨時變數

for (int i = 0; i < 5; i++)
                {
                    Task.Run(() =>
                      {
                          Thread.Sleep(100);
                          Console.WriteLine(i);  //列印結果 5個5
                      });
                }

                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        Thread.Sleep(100);
                        Console.WriteLine(k); //列印結果 4,3,1,2,0
                    });
                }

                //i最後是5      全程就只有一個i  等著列印的時候,i==5
                //k             全程有5個k   分別是0 1 2 3 4 
                //k在外面宣告   全程就只有一個k    等著列印的時候,k==4
                int k = 0;
                for (int i = 0; i < 5; i++)
                {
                    //    int k = i;
                    k = i;
                    new Action(() =>
                    {
                        Thread.Sleep(100);
                        Console.WriteLine($"k={k} i={i}");
                    }).BeginInvoke(null, null);
                }

 

 

四. 執行緒安全:

try
            {
                TaskFactory taskFactory = new TaskFactory();
                List<Task> taskList = new List<Task>();
 
                //如何判斷何時加鎖: 執行緒內部宣告的變數, 由於不共享的是執行緒安全的; 但是線上程外部操作的資源由於共享, 則就會造成執行緒不安全. 比如全域性變數/資料庫的某個值/磁碟檔案
                int TotalCountIn = 0;//TotalCountIn屬於執行緒外部變數, 會有執行緒安全有問題
                for (int i = 0; i < 10000; i++)//i屬於執行緒外部變數, 會有執行緒安全有問題
                {
                    int newI = i;//newI 屬於執行緒內部變數, 不會有執行緒安全有問題
                    taskList.Add(taskFactory.StartNew(() =>
                    {
                       //值型別不能lock
                        lock (btnThreadCore_Click_Lock)//lock後的方法塊,任意時刻只有一個執行緒可以進入  
                        //只能鎖引用型別,佔用這個引用連結   不要用string 因為享元  , 可能導致鎖的是同一塊記憶體區域
                        {   //這裡就是單執行緒
                             
                            this.TotalCount += 1;
                            TotalCountIn += 1;
                            this.IntList.Add(newI);
                        }

                    }));
                }
                Task.WaitAll(taskList.ToArray());
                Console.WriteLine(this.TotalCount);
                Console.WriteLine(TotalCountIn);
                Console.WriteLine(this.IntList.Count());
                #endregion 
            }
            catch (AggregateException aex) //專門處理多執行緒的異常
            {
                foreach (var item in aex.InnerExceptions)
                {
                    Console.WriteLine(item.Message);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

 

五.   關於Lock變數的寫法解釋:

//為什麼鎖的變數要這樣寫private static readonly object btnThreadCore_Click_Lock = new object();
//private  防止外面也去鎖     
//static 全場唯一  
//readonly不準修改, 如果不是readonly有可能會在鎖的程式碼裡面修改它  
//object表示引用, 因為lock不能鎖值型別

 

 六. 為什麼不推薦鎖this

 

//lock (this)
//{
//    //this form1的例項  每次例項化是不同的鎖,同一個例項是相同的鎖
//    //但是這個例項別人也能訪問到,別人也能鎖定
//    //最好不要鎖this
//}

 

七. Lock到底是個什麼? 

//Monitor.Enter(btnThreadCore_Click_Lock);
//lock 就是一個語法糖, 類似於monitor的寫法, 也就是說可以將lock{ }塊中的語句, 放到Monitor的Enter和exit中間
//Monitor.Exit(btnThreadCore_Click_Lock);
Monitor.Enter(btnThreadCore_Click_Lock);
//lock 就是一個語法糖, 類似於monitor的寫法, 也就是說可以將lock{ }塊中的語句, 放到Monitor的Enter和exit中間
{   //這裡就是單執行緒

     this.TotalCount += 1;
     TotalCountIn += 1;
     this.IntList.Add(newI);
}
Monitor.Exit(btnThreadCore_Click_Lock);

 

八. 不使用lock解決,執行緒安全問題:

//使用lock 解決的時候,因為只有一個執行緒可以操作資料, 沒有併發, 所以解決了問題    但是犧牲了效能,所以要儘量縮小lock的範圍

//解決辦法:1.  開發中最好不要有衝突, 能拆分就優先拆分, 比如在1億條資料中進行操作, 那麼可以先將資料拆分成不同的小塊, 然後再合併成1億條資料

//2. 不使用locak, 可以使用安全佇列 System.Collections.Concurrent.ConcurrentQueue來解決,   一個執行緒去完成操作