C# 多線程的幾種操作方式(異步委託、Thread、ThreadPool、Task【async/await】)

多線程主要用於多個任務並行執行,可以異步執行任務,提高響應速度,不阻塞當前線程(如C/S窗口)。

使用異步來調用以下代碼:

     //用於委託調用
     private void DoSomething(string name)
     {
            Console.WriteLine($"DoSomething被調用 {name}");
     }

一、委託異步調用:.net core中似乎不支持了

C#使用多線程離不開委託,定義一個委託也是可以直接使用多線程的。

     Action<string> action = this.DoSomething;
     action.Invoke("張三來了"); //同步調用
     action("張三來了"); //效果與上面的一樣

     AsyncCallback asyncCallback = result =>
     {
          Console.WriteLine($"asyncCallback被執行 {result.AsyncState}");
     };
     //參數一:委託方法的入參
     //參數二:異步執行完之後的回調函數(也是個委託)
     //參數三:需要傳給回調函數的數據(object類型),用result.AsyncState來獲取
     var asyncResult =   action.BeginInvoke("李四來了", asyncCallback,  "小二也來了");//異步調用
     action.EndInvoke(asyncResult);//阻塞異步,直到異步執行完成。如果是有返回值的異步調用(fun<>)。這裏可以接收到。
     asyncResult.AsyncWaitHandle.WaitOne();//阻塞異步,直到異步執行完成。效果與上面一樣,但是沒有返回值
     asyncResult.AsyncWaitHandle.WaitOne(-1);//阻塞異步,直到異步執行完成。效果與上面一樣
     asyncResult.AsyncWaitHandle.WaitOne(1000);//阻塞異步,但是隻阻塞1000毫秒,超時就不等了
     bool isCompleted = asyncResult.IsCompleted;//異步是否執行完成,可以使用while()來循環等待,如下所示:
     int i = 0;
     while (!asyncResult.IsCompleted)
     {
          Thread.Sleep(200);
          if(i<9)
               Console.WriteLine($"操作正在進行中。。。已完成{(++i)*10}%");
           else
               Console.WriteLine($"操作正在進行中。。。已完成99.99%");
     }
     Console.WriteLine($"操作已完成");

二、Thread類庫

Thread是C#對線程操作封裝好的工具類。

int result = 0; //假如 DoSomething 有返回值,用於接收返回值: thread.Join(); 之後使用result。
Thread thread = new Thread(() => {
    DoSomething("王五來了");
    result = 5;
});
//不常用方法
//thread.Suspend();//暫停線程,不建議使用
//thread.Resume();//恢復贊停的線程,不建議使用
//thread.Abort(); //銷燬線程,主線程讓子線程拋出異常的方式來結束線程,可能會有延時,不一定能真的停下來。不建議使用
//Thread.ResetAbort();//恢復已銷燬線程,不建議使用

//常用方法
while (thread.ThreadState != ThreadState.Stopped)//等待線程執行完成
{
    Thread.Sleep(200);
}
thread.Join();//運行這句代碼的線程,等待thread完成。 可以在主線程等待,也可以在其它子線程等待
thread.Join(1000);//最多隻等到1000ms
thread.Priority = ThreadPriority.Highest;//設置線程的執行優先級
//是否後臺線程,false:非後臺線程(進程關閉,子線程執行完才退出),true:是後臺線程(進程關閉,子線程也退出)
thread.IsBackground = false;

//模擬異步回調:其實說白了就是異步執行完了再執行另一個函數。
Thread thread1 = new Thread(() => {
    DoSomething("王五來了");
    DoSomething("這是回調");
});

三、ThreadPool

線程池(ThreadPool),創建了多個線程對象,需要用的時候直接從線程池中取,避免了對象的創建和銷燬等代價(享元設計模式)。節約資源 提升性能,控制線程數量,防止濫用。

ThreadPool.QueueUserWorkItem((obj) => DoSomething("王五來了") );

//委託的參數obj就是QueueUserWorkItem的第二個參數。
ThreadPool.QueueUserWorkItem((obj) => DoSomething($"王五來了({obj.ToString()})"),"王五的兒子");

//獲取線程池中輔助線程的最大數量(workerThreadsMax)和線程池中異步I/O線程的最大數量(completionPortThreadsMax)
//獲取線程池中輔助線程的最小數量(workerThreadsMin)和線程池中異步I/O線程的最小數量(completionPortThreadsMin)
ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);
ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);

//設置最大線程數量 和 設置最小線程數量,在進程內是全局的。在一個地方設置了,後面所有的請求中都是這個數量了
//委託異步調用、Task、Parallel、async/await 都使用的是線程池的線程; new Thread()不受限制,但是會佔用線程池的數量。
ThreadPool.SetMaxThreads(12, 12);//不能低於當前電腦的線程數;比如四核八線程,就不能低於8,否則無效
ThreadPool.SetMinThreads(1, 1);

//線程等待,需要使用ManualResetEvent來完成
ManualResetEvent mre = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((obj) => {
    DoSomething("王五來了");
    mre.Set();
} );
mre.WaitOne();

四、Task

基於任務的異步模型。使用的還是線程池裏面的線程。

//*********************************Task使用方式1*******************************************
Task task1 = new Task(() => { DoSomething("張三"); });
task1.Start();

//*********************************Task使用方式2*******************************************
var taskFactory = Task.Factory;
Task<int> t1 = taskFactory.StartNew<int>(() => { DoSomething("王五"); return 1; });
Task t2 = taskFactory.StartNew(() => { DoSomething("趙六"); });

Task t3 = taskFactory.ContinueWhenAll(new Task[] { t1, t2 }, (t) =>
 {
     Console.WriteLine("所有線程都完成了,就會調用這個函數, 不會阻塞主線程");
 });
Task t4 = taskFactory.ContinueWhenAny(new Task[] { t1, t2 }, (t) =>
{
    Console.WriteLine("任意一個線程完成了,就會調用這個函數, 不會阻塞主線程");
});

//*********************************Task使用方式3*******************************************
Task<int> task = Task.Run<int>(() => { DoSomething("李四"); return 1; }); //使用方式3
int temp = task.Result; //阻塞執行完畢並獲取結果
task.Wait(); //阻塞,直到子線程執行完畢
await task; //阻塞,直到子線程執行完畢。 但是主線程(調用方)將繼續往下執行,await task後面的代碼等同於封裝在ContinueWith()裏面

 //*********************************Task其它使用方式*******************************************

 //Task.Delay(2000)不阻塞當前線程,一般配合ContinueWith使用,在ContinueWith裏面的子線程將等待2秒之後執行
 //Thread.Sleep(2000)是阻塞當前線程
 Task task3 = Task.Delay(2000).ContinueWith((t) =>
{
});

//等待所有線程完成
Task.WaitAll(new Task[] { task1, t1 });
//等待任意一個線程完成
Task.WaitAny(new Task[] { task1, t1 });
//線程回調
task1.ContinueWith((o) =>
{
     Console.WriteLine("線程回調,task1執行完畢之後執行這裏。");
});

五、async/await(C/S建議使用)

async/await是C#保留關鍵字,需要結合Task使用。

async用於標識異步方法,方法內部有兩種情況:

1、沒有await:調用方降等待異步方法執行完畢才能接着往下執行,異步方法中的子線程還是並行執行的。

2、包含await:調用方執行異步方法,遇到await後,調用方可以立馬往下執行,不需要等待異步方法執行完畢,這個時候“子線程和await task後面的代碼”與“調用方”是並行執行的。這裏的await指的是在創建子線程(Task.Run)的地方await,且調用方調用異步方法的時候前面沒有加await。但是異步函數內部執行到await關鍵字後會等待子線程執行完畢。

      感覺就像是將await後面的代碼封裝到ContinueWith()裏面執行一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章