Csharp線程

来源:https://www.cnblogs.com/wenlong-4613615/p/18101331
-Advertisement-
Play Games

CSharpe線程 目錄CSharpe線程C#如何操作線程Thread1. Thread如何開啟一個線程呢?2. Thread中常見的API3. thread的擴展封裝threadpool一、 .NET Framework2.0時代:出現了一個線程池ThreadPool二、線程池如何申請一個線程呢? ...


CSharpe線程

目錄

什麼是進程?

一個應用程式的運行---對標於一個進程----虛擬詞;
所謂的進程---記錄了程式運行所消耗的各種各樣的資源;

什麼是線程?

就是電腦程式在運行的時候,執行指令的最小的執行流~ 程式
的運行---很多的併發操作,任何一個指令的執行都是需要通過線程來完成;
一個進程至少要包含一個線程;進程退出,線程也是自動消失;

什麼是多線程?

隨著技術的發展---業務的需求---需要指令的併發執行;
同時執行多種指令(線程來執行的);
和CPU的核數有關~~

C#如何操作線程

  1. Thread(很少用)
  2. ThreadPool(線程池)
  3. Task(主流-----重點)

Thread

Thread:來自於System.Threading的一個密封類,它是在.net Framwork1.0時代出現的,在C#中用來操作電腦資源線程的一個幫助類庫;

1. Thread如何開啟一個線程呢?

多線程因為是無序的,調試不太好調試,只能通過寫日誌,輸出結果,根據結果來判斷thread的特點.

private void btn_Thread_Click(object sender, EventArgs e)
{
    Debug.WriteLine($"***************Main Thread start: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");

    Thread thread = new Thread(() =>
    {
        Debug.WriteLine($"***************Thread start: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
       
        Debug.WriteLine($"***************Thread end: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
    });

    thread.Start();

    Debug.WriteLine($"***************Main Thread end: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
}

結果

img

2. Thread中常見的API

thread.Suspend(); // 線程暫停
thread.Resume();  // 線程恢復
thread.Abort();   // 線程終止

1.線程等待的:ThreadState有多種狀態;如果線程停止了,狀態會修改;

while (thread.ThreadState != System.Threading.ThreadState.Stopped) //如果線程沒有停止;
{
    Thread.Sleep(500); //當前休息500ms  不消耗電腦資源的
}

2.自己支持的線程等待: 
thread.Join();//等待線程中的內容執行完畢;繼續往後; 
thread.Join(500);//等待500ms,過時不候;
thread.Join(TimeSpan.FromMilliseconds(500));//等待500ms,過時不候;


thread.IsBackground = true;// 是後臺線程:程式強制關掉,線程也就隨之消失了; 
thread.IsBackground = false; //是前臺線程:程式強制關掉,線程會等待,內部的行為執行完畢,然後才結束;
thread.Start(); 

3. thread的擴展封裝

多線程;非同步執行;
不阻塞界面;
無序性---多個動作。如果使用多線程,是無法控制順序的。

現在有兩個動作 使用了2個委托 必須是多線程執行的 要求兩個委托按順序執行。

 Action action = () => { Debug.WriteLine("this is first run"); };

 Action action2 = () => { Debug.WriteLine("this is second run"); };

 private void button1_Click(object sender, EventArgs e)
 {
     callBack(action, action2);
 }

 private void callBack(Action action,Action action1)
 {
    Thread t= new Thread(() =>
    {
        action();
        action1();
    });
     t.Start();
 }

如果有一個帶返回值的委托,需要你要多線程執行;

 Func<int> func = () => { return DateTime.Now.Year; };

 private void button1_Click(object sender, EventArgs e)
 {
     Func<int> func1= CallBack<int>(func);
     Debug.WriteLine("t****************");
     Debug.WriteLine("t****************");
     Debug.WriteLine("t****************");
     Debug.WriteLine("t****************");
     Debug.WriteLine("t****************");
     Debug.WriteLine("t****************");
     int iResult=func1();
     Debug.WriteLine(iResult);

 }    
 private Func<T> CallBack<T>(Func<T> func)
 {
     T result = default(T);
     Thread t = new Thread(() =>
     {
         result = func();
     });
     t.Start();

     return new Func<T>(() => { 
         t.Join();
         return result; });
    
 }

threadpool

Thread對比Threadpool:Api很多,功能繁多;使用起來,不好控制;讓開發者試用起來並不友好;
Thread對線程的數量管控,全部都需要讓程式員自己來管控;

一、 .NET Framework2.0時代:出現了一個線程池ThreadPool

是一種池化思想,相當於是在池子中,有線程存在;如果需要使用線程;就可以直接到線程池中去獲取直接使用,如果使用完畢,在自動的回放到線程池中去;

好處:
1.不需要程式員對線程的數量管控,提高性能,放置濫用
2.去掉了很多在Thread中沒有必要的Api

二、線程池如何申請一個線程呢?

 ThreadPool.QueueUserWorkItem((state) =>
 {
     Debug.WriteLine($"***************ThreadPool start: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
     Thread.Sleep(5000);
     Debug.WriteLine($"***************ThreadPool end: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
 });

三、線程等待

  1. 觀望式的:
  2. 定義一個監聽ManualResetEvent
  3. 通過ManualResetEvent.WaitOne等待
  4. 等到ManualResetEvent.Set方法執行,方法執行完畢後,主線程就繼續往後;
            ManualResetEvent manualResetEvent = new ManualResetEvent(false);
            ThreadPool.QueueUserWorkItem((state) =>
            {
                Debug.WriteLine($"***************ThreadPool start: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
                Thread.Sleep(5000);
                Debug.WriteLine($"***************ThreadPool end: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
                manualResetEvent.Set();
            });
            manualResetEvent.WaitOne();
            Debug.WriteLine($"***************Main Thread end: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
        }

四、線程池如何控制線程數量

如果通過SetMinThreads/SetMaxThreads來設置線程的數量;這個數量訪問是在當前進程是全局的;

 {
     int workerThreads = 4;
     int completionPortThreads = 4;
     ThreadPool.SetMinThreads(workerThreads, completionPortThreads);
 }
 {
     int workerThreads = 8;
     int completionPortThreads = 8;
     ThreadPool.SetMaxThreads(workerThreads, completionPortThreads);
 }
 {
     ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
     Debug.WriteLine($"當前進程最小的工作線程數量:{workerThreads}");
     Debug.WriteLine($"當前進程最小的IO線程數量:{completionPortThreads}");
 }
 {
     ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
     Debug.WriteLine($"當前進程最大的工作線程數量:{workerThreads}");
     Debug.WriteLine($"當前進程最大的IO線程數量:{completionPortThreads}");
 } 

Task

一、Task開啟線程有哪些方式

Action action = () =>
{
    Console.WriteLine($"***************Task start: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
    Console.WriteLine("啟動了一個新的線程");
    Console.WriteLine($"***************Task end: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
};

Task task = new Task(action);
task.Start();

Task.Run (() =>
{
    Console.WriteLine($"***************Task.Run start: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
    Console.WriteLine("啟動了一個新的線程");
    Console.WriteLine($"***************Task.Run end: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
});


TaskFactory taskFactory = new TaskFactory();
taskFactory.StartNew(() =>
{
    Console.WriteLine($"***************TaskFactory start: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
    Console.WriteLine("啟動了一個新的線程");
    Console.WriteLine($"***************TaskFactory end: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
});

Task.Factory.StartNew(() =>
{
    Console.WriteLine($"***************Task.Factory start: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
    Console.WriteLine("啟動了一個新的線程");
    Console.WriteLine($"***************Task.Factory end: {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("HH:mm:ss.fff")} **************** ");
});

啟動的多線程的特點:

  1. 不阻塞主線程----不會卡頓界面
  2. 線程的啟動---由操作系統來調度啟動; 延遲啟動(延遲很短)
  3. 併發執行~~

線程執行完畢就銷毀了嗎?
ThreadPool 線程池----Task 線程都是來自於線程池的;

多進程技術的使用場景的分析

問題:儘可能的多啟動線程?? 萬萬不可的,一定要適當的使用;
一堆業務邏輯: 項目要開發 10個板塊
單線程執行: 一個人去承擔這個項目開發----一步一步的做;一個版快一個板塊的去開發; 開發周期時間長
多線程執行: 一個團隊開發: 效率更高~~ 多個人可以分工開發;
類比: 一個人(開支小)和一個團隊(10個人 10份工資);

線程等待

有Delay 和 Sleep兩種方式來進行線程的等待.

{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    Task.Delay(3000);
    stopwatch.Stop();
    Console.WriteLine($"time:{stopwatch.ElapsedMilliseconds}");
}


{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    Thread.Sleep(3000);
    stopwatch.Stop();
    Console.WriteLine($"time:{stopwatch.ElapsedMilliseconds}");
}

結果為:

img

Task.Delay().ContinueWith() 不阻塞主線程,等待多長時間之後,可以執行一段業務邏輯----回調函數
Thread.Sleep() 阻塞主線程,主線程等待指定時間後再運行。

線程等待的多種方案

  Task<int> task = Task.Run(() =>
  {
      Thread.Sleep(3000);

      Console.WriteLine("Open new thread!");

      return 10;
  });

  int num = task.Result; //等待task執行完畢,獲取返回值,會阻塞當前線程

   //下麵是沒有返回值方法調用的時候,使用的方法
     //Task.WaitAll(task); //等待task執行完畢,會阻塞當前線程

    //int i = Task.WaitAny(task); //等待task執行完畢,會阻塞當前線程

什麼場景下可以使用多線程呢?(可以併發的時候) 不適合使用多線程??
故事: 高級班的項目實戰---逐個講解知識點,然後項目實戰,分工合作,分小組開發;

  1. 逐個講解知識點 -----可以多線程來模擬?---只有Richard老師一個人講解----不可用;不能併發,不能多線程來模擬
  2. 項目實戰,分工合作,分小組開發; -----可以多線程來模擬?---有多個人同時開發,可以分工併發開發,可以多線程開發~~

模擬的代碼

 /// <summary>
 /// 模擬講課的方法
 /// </summary>
 /// <param name="lesson">課程名</param>
 private void Tech(string lesson)
 {
     Console.WriteLine($"{lesson} ||開始了.....");
     long iResult = 0;

     for (int i = 0;i<1_000_000_000;i++)
     {
         iResult += i;
     }
     Console.WriteLine($"{lesson} ||講完了.....");
 }
 /// <summary>
 /// 模擬不同人開發的方法
 /// </summary>
 /// <param name="name"></param>
 /// <param name="projectName"></param>
 private void Coding(string name,string projectName)
 {
     Console.WriteLine($"************************* Coding Start || {name}  {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} ****************");
     long iResult = 0;

     for (int i = 0; i < 1_000_000_000; i++)
     {
         iResult += i;
     }
     Console.WriteLine($"************************* Coding End || {name}  {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} ****************");
 }

基礎的項目流程

private void button5_Click(object sender, EventArgs e)
{
    Console.WriteLine("同學們!開始上課了");
    Tech("泛型");
    Tech("委托");
    Tech("多線程");
    Tech("非同步編程");
    Tech("併發編程");
    Console.WriteLine("知識點講解完畢了~~開始項目實戰開發`~~~");
    TaskFactory factory = new TaskFactory();

    factory.StartNew(() => Coding("張三", "資料庫設計"));
    factory.StartNew(() => Coding("李四", "框架的搭建"));
    factory.StartNew(() => Coding("王五", "Wechat Pay"));
    factory.StartNew(() => Coding("趙六", "Web Api"));
    factory.StartNew(() => Coding("田七", "封裝通用的組件"));                
    factory.StartNew(() => Coding("劉八", "編譯"));
    factory.StartNew(() => Coding("楊九", "發行"));

    
}

需求一、所有人的任務都執行完成後,小聚一下,大吃一頓```

  private void button5_Click(object sender, EventArgs e)
  {

      List<Task> tasks = new List<Task>();
      Console.WriteLine("同學們!開始上課了");
      Tech("泛型");
      Tech("委托");
      Tech("多線程");
      Tech("非同步編程");
      Tech("併發編程");
      Console.WriteLine("知識點講解完畢了~~開始項目實戰開發`~~~");
      TaskFactory factory = new TaskFactory();

      tasks.Add( factory.StartNew(() => Coding("張三", "資料庫設計")));
      tasks.Add(factory.StartNew(() => Coding("李四", "框架的搭建")));
      tasks.Add(factory.StartNew(() => Coding("王五", "Wechat Pay")));
      tasks.Add(factory.StartNew(() => Coding("趙六", "Web Api")));
      tasks.Add(factory.StartNew(() => Coding("田七", "封裝通用的組件")));
      tasks.Add(factory.StartNew(() => Coding("劉八", "編譯")));
      tasks.Add(factory.StartNew(() => Coding("楊九", "發行")));

      Task.WaitAll(tasks.ToArray());
      Console.WriteLine("項目開發完畢了~~~,去大吃一頓~~");      
  }

需求2、 開發人員中,只要其中有一個執行完成了,Richard老師就準備發佈環境,準備發佈部署

 private void button5_Click(object sender, EventArgs e)
 {

     List<Task> tasks = new List<Task>();
     Console.WriteLine("同學們!開始上課了");
     Tech("泛型");
     Tech("委托");
     Tech("多線程");
     Tech("非同步編程");
     Tech("併發編程");
     Console.WriteLine("知識點講解完畢了~~開始項目實戰開發`~~~");
     TaskFactory factory = new TaskFactory();

     tasks.Add( factory.StartNew(() => Coding("張三", "資料庫設計")));
     tasks.Add(factory.StartNew(() => Coding("李四", "框架的搭建")));
     tasks.Add(factory.StartNew(() => Coding("王五", "Wechat Pay")));
     tasks.Add(factory.StartNew(() => Coding("趙六", "Web Api")));
     tasks.Add(factory.StartNew(() => Coding("田七", "封裝通用的組件")));
     tasks.Add(factory.StartNew(() => Coding("劉八", "編譯")));
     tasks.Add(factory.StartNew(() => Coding("楊九", "發行")));
     {
         Task.WaitAny(tasks.ToArray());  //等待一堆任務中,其中有一個執行完成了,繼續往後執行~
         Console.WriteLine("XXX 完成了開發任務~~,Richard老師就準備發佈環境,準備發佈部署");
     }
     {
         Task.WaitAll(tasks.ToArray());
         Console.WriteLine("項目開發完畢了~~~,去大吃一頓~~");
     }
     
 }

使用場景:
Task.WaitAll----系統首頁---包含了很多的信息---都是後臺提供----獲取這個結果的時候;準備一個複雜實體---包含各種信息 查詢這些數據---可以多線程去執行;同時查詢;
查詢必須要獲取到所有的數據----要獲取所有的數據----Task.WaitAll

Task.WaitAny----查詢一條數據----數據來源可能是不同的地方,資料庫/緩存/介面/讀取硬碟中的數據
1.傳統做法: 先查詢緩存試試看,如果沒有,再查詢資料庫,如果沒有,再繼續往後,直到查詢到數據為止;
2.有四個渠道獲取數據----> 只要有一個渠道獲取到數據就Ok, 直接啟動四個線程去查詢; 等待其中有一個線程執行完成,特殊處理,如果查詢到數據後,就結束~~ 只要有一個執行結束了,就已經拿到數據了,其他的不用管了~~

需求3、 有沒有可以不阻塞主線程,也能達到效果;

 private void button2_Click(object sender, EventArgs e)
 {
     List<Task> tasks = new List<Task>();
     Console.WriteLine("同學們!開始上課了");
     Tech("泛型");
     Tech("委托");
     Tech("多線程");
     Tech("非同步編程");
     Tech("併發編程");
     Console.WriteLine("知識點講解完畢了~~開始項目實戰開發`~~~");
     TaskFactory factory = new TaskFactory();

     tasks.Add(factory.StartNew(Object => Coding("張三", "資料庫設計"),"張三"));
     tasks.Add(factory.StartNew(Object => Coding("李四", "框架的搭建"), "李四"));
     tasks.Add(factory.StartNew(Object => Coding("王五", "Wechat Pay"), "王五"));
     tasks.Add(factory.StartNew(Object => Coding("趙六", "Web Api"), "趙六"));
     tasks.Add(factory.StartNew(Object => Coding("田七", "封裝通用的組件"), "田七"));
     tasks.Add(factory.StartNew(Object => Coding("劉八", "編譯"), "劉八"));
     tasks.Add(factory.StartNew(Object => Coding("楊九", "發行"), "楊九"));
   

     {
         factory.ContinueWhenAny(tasks.ToArray(), (task) =>
         {
             Console.WriteLine($"{task.AsyncState} 完成了開發任務~~,發一個小紅包");
         });
     }
     {
         factory.ContinueWhenAll(tasks.ToArray(), (task) =>
         {
             Console.WriteLine("項目開發完畢了~~~,去大吃一頓~~");
         });
         
     }
 }

需求4、如果想要完成以上需求,要求不阻塞主線程,如果也沒有ContinueWhenAll api.


Task.Run(() =>
{
    Task.WaitAll(tasks.ToArray());
    Console.WriteLine("項目開發完畢了~~~,去大吃一頓~~");
});

通過Task返回一個字元串

{
    List<Task<string>> tasklist = new List<Task<string>>();
    for (int i = 0; i < 3; i++)
    {
        string k = $"{i}";
        tasklist.Add(Task.Run(() =>
        {
            return $"{k}_Task";
        }));
    }
    Task.Run(() =>
    {
        Task.WaitAny(tasklist.ToArray());
        Task<string> task = tasklist.First(c => c.Status == TaskStatus.RanToCompletion);
        Console.WriteLine(task.Result);
    });
}

Paralell

如何批量開啟10個線程?

 Parallel.For(0, 10, (i) =>
 {
  

 Console.WriteLine($"Thread id : {Thread.CurrentThread.ManagedThreadId.ToString("00")  }") ;

 });

如何控制啟動線程的數量?

 ParallelOptions parallelOptions = new ParallelOptions();
 parallelOptions.MaxDegreeOfParallelism = 10;

 Parallel.For(0, 10100, parallelOptions, (i) =>
 {
     Console.WriteLine($"Thread id : {Thread.CurrentThread.ManagedThreadId.ToString("00")  }");

 });

線程異常處理

1.try_catch捕獲不到多線程內部的異常.

按照正常的Try Catch來處理異常。

try {
    for (int i = 0; i < 20; i++)
    {
        string str = $"Advance_{i}";
        Task.Run(() =>
        {
            if (str.Equals("Advance_7"))
            {
                throw new Exception("Advance_7異常");
            }
            else if (str.Equals("Advance_10"))
            {
                throw new Exception("Advance_{10}異常");
            }
            else if (str.Equals("Advance_15"))
            {
                throw new Exception("Advance_15異常");
            }
            else if (str.Equals("Advance_18"))
            {
                throw new Exception("Advance_18異常");
            }
            else
            {
                Console.WriteLine(str);
            }
        });


    }
} 
catch (Exception ex) 
{ 
    Console.WriteLine(ex.Message); 
}

2.如何捕捉線程內部的異常,try-catch 包裹,線程等待; 可以捕捉到AggregateException類型的異常;
3.一個try可以對應多個catch 發生異常後,catch捕捉,是從上往下匹配異常類型,只要是匹配到異常類型後,就進入開始處理異常;
4.如何輸出消息, 要轉換成AggregateException,獲取InnerExceptions 的集合,多線程發生的多個異常,都在這個集合中;

  private void button4_Click(object sender, EventArgs e)
  {

      List<Task> tasks = new List<Task>();
      try {
          for (int i = 0; i < 20; i++)
          {
              string str = $"Advance_{i}";
              Task task = Task.Run(() =>
              {
                  if (str.Equals("Advance_7"))
                  {
                      throw new Exception("Advance_7異常");
                  }
                  else if (str.Equals("Advance_10"))
                  {
                      throw new Exception("Advance_{10}異常");
                  }
                  else if (str.Equals("Advance_15"))
                  {
                      throw new Exception("Advance_15異常");
                  }
                  else if (str.Equals("Advance_18"))
                  {
                      throw new Exception("Advance_18異常");
                  }
                  else
                  {
                      Console.WriteLine(str);
                  }
              });
              tasks.Add(task);
          }
          Task.WaitAll(tasks.ToArray());
      } 
      catch (Exception ex) 
      { 
          Console.WriteLine(ex.Message); 
      }
      
  }

線程取消

有一個需求:
首頁---數據塊---考情/周top10/月top ......
啟動四個線程去獲取數據,要正常展示----一定要四個線程都能正常獲取到數據,必然要等待四個線程都執行結束;
場景:四個線程,有某一個線程異常了~~ 整塊數據不能用; 如果有異常,其他的正常的線程,其實查詢也沒有價值,既然沒有異常的線程執行也沒價值,就應該取消-----(因為線程在執行業務邏輯---需要消耗電腦的資源,電腦的資源是有限的)

標準方案:

定義一個cts,包含一個IsCancellationRequested 屬性,預設值為=false,同時提供了一個Cancel方法, IsCancellationRequested: 預設的false ----true; IsCancellationRequested 屬性 只能通過Cancel來變化,不能通過其他的渠道修改;

 CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
 
 
 try {
     for (int i = 0; i < 50; i++)
     {
         string str = $"Advance_{i}";
         Task.Run(() =>
         {
             if (cancellationTokenSource.IsCancellationRequested == false)
             {
                 Console.WriteLine("正常運行");

                 if (str.Equals("Advance_7"))
                 {
                     cancellationTokenSource.Cancel();
                     throw new Exception("Advance_7異常");
                 }
                 else if (str.Equals("Advance_10"))
                 {
                     cancellationTokenSource.Cancel();
                     throw new Exception("Advance_{10}異常");
                 }
                 else if (str.Equals("Advance_15"))
                 {
                     cancellationTokenSource.Cancel();
                     throw new Exception("Advance_15異常");
                 }
                 else if (str.Equals("Advance_18"))
                 {
                     cancellationTokenSource.Cancel();
                     throw new Exception("Advance_18異常");
                 }

             }
             else
             {
                 Console.WriteLine("線程非正常退出");
             }
         });
         
     }
     
 } 
 catch (Exception ex) 
 { 
     Console.WriteLine(ex.Message); 
 }

多線程的中間變數

先看一段代碼

for (int i = 0; i < 10000; i++)
{
    Task.Run(() => Console.WriteLine($"{i}"));
}

輸出的都是10000

為什麼會這樣那:
int i = 0; 開始迴圈,定義好的一個變數;
線程是延遲啟動,啟動線程不阻塞UI線程; 多線程要執行邏輯,要使用i,i已經是20了;

要實現我們的目的

 for (int i = 0; i < 10000; i++)
 {
     int k = i;

     Task.Run(() => Console.WriteLine($"{k}"));
 }

線程安全

線程不安全:多線程在執行業務邏輯的時候,得到的結果,如果和單線程執行的結果如果不一致,那就是線程不安全~~
線程安全:單線程執行的結果要和多線程執行的結果要一致;線程安全的;

有多線程不安全的代碼

  private void button6_Click(object sender, EventArgs e)
  {
      List<int> list = new List<int>();
      List<Task> tasks = new List<Task>();
      for (int i = 0; i < 10000; i++)
      {
          tasks.Add(Task.Run(() => { list.Add(i); }));
      }

      Task.WaitAll(tasks.ToArray());
      Console.WriteLine(list.Count);
  }

如何解決線程安全呢?

  1. 鎖, ----控制執行的線程只能有一個
  2. 直接使用單線程;
  3. 使用線程安全對象 看看數據結構 線程安全對象 List/Arraylist 都不是線程安全的集合--把list Arraylist 換成安全對象;
  4. 通過演算法+拆分做到---劃塊操作數據; 原理:還是單線程去操作一塊數據;
 private readonly static object obj = new object();
 private void button6_Click(object sender, EventArgs e)
 {
     List<int> list = new List<int>();
     List<Task> tasks = new List<Task>();
     for (int i = 0; i < 10000; i++)
     {

         tasks.Add(Task.Run(() =>
         {
             //鎖: 控制鎖內部的代碼執行,只能有一個線程進入執行,必須要等進入鎖的線程執行結束了,其他的線程才能再進去一個; 反多線程;
             lock (obj)
             {
                 list.Add(i);
             }
         }));


     }

     Task.WaitAll(tasks.ToArray());
     Console.WriteLine(list.Count);
 }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 面向對象編程(OOP)是一種使用對象及其相互作用設計應用和電腦程式的編程範例。 OOP 中有一些基本的編程概念: 抽象化 (抽象化,也在我們編程世界中 所有類都是抽象化,物以類聚,擁有共同的特性或者行為) 椅子類 人類 動物類 【本質就是歸類】 多態性 【一類多種表現形態】【本質就是抽象化的程度】 ...
  • Avalonia中的Window 在Avalonia中,Window是一個基本的UI元素,它代表了一個應用程式的視窗。每個Window都可以包含其他的UI元素,如按鈕、文本框等,並可以響應各種用戶輸入事件。 在下麵的例子中,制定了當前應用的Window是MainWindow public parti ...
  • 目錄1.Redis簡介2.使用場景3.C# 具體使用介紹(Nuget)StackExchange.RedisFreeRedisNewLife.RedisServiceStack.Redis (收費)4.Redis 常用面試問題以及回答5.建議及經驗分享建議Redis 經驗分享ShareFlow 1. ...
  • 上一篇介紹了 IL 指令的分類以及參數載入指令,該載入指令以ld開頭,將參數載入到棧中,以便於後續執行操作命令。本篇開始介紹參數存儲指令,其指令以st開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。 ...
  • 引言 在現代化的軟體開發中,單元測試和集成測試是確保代碼質量和可靠性的關鍵部分。ASP.NET Core 社區內提供了強大的單元測試框架,xUnit 是其中之一,它提供了簡單、清晰和強大的測試功能,編寫單元測試有許多優點;有助於回歸、提供文檔及輔助良好的設計。下麵幾節我們來深入淺出探討如何使用 xU ...
  • 隨著跨平臺應用的需求不斷增長,開發人員需要一種能夠在不同操作系統上運行的用戶界面(UI)框架。 Avalonia 是一種引人註目的選擇。在本文中,我們將深入瞭解 Avalonia 是什麼,它與 WPF 的區別,以及它的 UI 繪製引擎和原理、優點,以及一個簡單的示例代碼。 Avalonia 是什麼? ...
  • iNeuOS工業互聯網操作系統“表單設計”功能經過升級後,能夠適用於更多應用場景,從業務上來講可以擴展設備管理、MES等表單類的管理功能,從技術上來講可以支持資料庫單表應用、多級關聯表應用、可以自定義寫SQL語句等,現在支持22個基礎表單組件、9個高級表單組件。 ...
  • 本篇教程深入探討了 ILGenerator 中的參數載入指令,通過詳細解釋Ldarg、Ldarga、Ldloc和Ldloca等指令的使用,讀者能夠清晰地認識到Ld指令用於載入參數或本地變數到堆棧,而St指令用於將值從堆棧存儲到參數或本地變數中。這些指令為動態方法的生成提供了基礎,幫助開發者更好地掌握... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...