多線程主要用於多個任務並行執行,可以異步執行任務,提高響應速度,不阻塞當前線程(如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()裏面執行一樣。