|
多線程,一個(gè)多么熟悉的詞匯,作為一名程序員,我相信無(wú)論是從事什么開發(fā)語(yǔ)言,都能夠輕輕松松說(shuō)出幾種實(shí)現(xiàn)多線程的方式,并且在實(shí)際工作種也一定用到過(guò)多線程,比如:定時(shí)器、異步作業(yè)等等,如果你說(shuō)你沒有用過(guò)多線程,我懷疑你是不是一名程序員,哈哈。 哈哈,言歸正傳,今天我們要說(shuō)說(shuō)c#中的多線線程哪一些事,當(dāng)然c#在實(shí)現(xiàn)多線程上有多種方式,比如:Threads、Action、ThreadPool、Task、Parallel等,當(dāng)然每一種方式都用其優(yōu)點(diǎn)和缺點(diǎn),也有其應(yīng)用場(chǎng)景,在此不一一說(shuō)明,今天我們主要以task為例,來(lái)一起聊聊task的使用,也就當(dāng)著一次自我總結(jié)提煉罷了。
為什么要用多線程
其實(shí)我們?cè)趯?shí)際使用過(guò)程中,使用多線程的目的其實(shí)即使為了實(shí)現(xiàn)異步+并行,異步:是相對(duì)同步的,同步就是一個(gè)流程安裝一個(gè)流程執(zhí)行完畢,異步就是在不影響主流程的執(zhí)行同時(shí),可以執(zhí)行其他流程,這也就是達(dá)到了幾個(gè)邏輯并行執(zhí)行的效果。當(dāng)然了,不是說(shuō)異步就完全是獨(dú)立執(zhí)行,相互間就沒有關(guān)聯(lián)關(guān)系,其實(shí)在異步的同時(shí),也可以在特定節(jié)點(diǎn)等待阻塞等待異步結(jié)果啦。說(shuō)了半天廢話,不要走開,主題才剛剛開始,下面以實(shí)際例子來(lái)演繹task的實(shí)際使用吧!
如何創(chuàng)建和運(yùn)行一個(gè)task
微軟就是那么6逼,在創(chuàng)建和執(zhí)行一個(gè)task時(shí),都給大家提供了多種方式來(lái)實(shí)現(xiàn),大家可以根據(jù)其具體的使用場(chǎng)景和習(xí)慣來(lái)選擇最適合的方式,下面通過(guò)代碼來(lái)簡(jiǎn)單說(shuō)明如下的三種實(shí)現(xiàn)方式: /// <summary>
/// 簡(jiǎn)單的task創(chuàng)建方式演示 /// </summary>
private static void TaskCreatFun()
{ // 其一、通過(guò)傳統(tǒng)的 new 方式來(lái)實(shí)例化一個(gè)task對(duì)象,這種方式需要手動(dòng)通過(guò)start來(lái)啟動(dòng)
Task newTask = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Hello Engineer, 我是 new 的一個(gè)task,線程ID:Thread.CurrentThread.ManagedThreadId}");
}); // 啟動(dòng) tsak newTask.Start(); // 其二、通過(guò)工廠 factory 來(lái)生成一個(gè)task對(duì)象,并自啟動(dòng)
Task factoryTask = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Hello Engineer, 我是 factory 生產(chǎn) 的一個(gè)task,線程ID:Thread.CurrentThread.ManagedThreadId}");
}); // 其三、通過(guò) Task.Run(Action action) 來(lái)創(chuàng)建一個(gè)自啟動(dòng)task
Task runTask = Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Hello Engineer, 我是 Task.Run 創(chuàng)建一個(gè)自啟動(dòng)task,線程ID:Thread.CurrentThread.ManagedThreadId}");
});
runTask.RunSynchronously();
Console.WriteLine($"Hello Engineer, 我是主線程啦!線程ID{Thread.CurrentThread.ManagedThreadId}");
}
代碼的執(zhí)行結(jié)果:很容易看出,task執(zhí)行阻塞主線程,并且?guī)讉€(gè)task并行執(zhí)行。
如何創(chuàng)建一個(gè)帶有返回值的task
在上面的代碼實(shí)例種,我們以不同的方式創(chuàng)建并運(yùn)行了一個(gè)多線程,但是這幾個(gè)task都是沒有返回值的,這樣的task適用于:獨(dú)立執(zhí)行的一個(gè)子業(yè)務(wù),完全不關(guān)心其執(zhí)行結(jié)果。但是我們?cè)趯?shí)際使中,往往會(huì)需要關(guān)系其執(zhí)行結(jié)果的。 以一個(gè)實(shí)際的業(yè)務(wù)場(chǎng)景來(lái)說(shuō)明:比如,我們?cè)谝粋€(gè)酒店預(yù)訂系統(tǒng)中,需要實(shí)時(shí)到不同的第三接口實(shí)時(shí)查詢某一個(gè)酒店的某一客房在最新狀態(tài),比如有3個(gè)接口渠道:攜程、藝龍、去哪兒,該如何實(shí)現(xiàn)呢? 首先,如果我們采用串行的方式一個(gè)一個(gè)的去取數(shù)據(jù),那樣估計(jì)你的系統(tǒng)慢到不會(huì)有人用的,所以我們第一個(gè)想到的是,采用task來(lái)并行獲取,并返回獲取到的結(jié)果值,下面簡(jiǎn)單模擬一下代碼實(shí)現(xiàn): /// <summary>
/// 獲取最新的客房信息 /// </summary>
/// <returns>客房信息集合</returns>
private static List<string> GetHotelRoomInfro()
{ // 模擬存儲(chǔ)獲取到的酒店客房數(shù)據(jù)集合
List<string> listHotelRoomInfro = new List<string>();
Console.WriteLine("下面通過(guò)3個(gè)task,并行的到不同接口方獲取實(shí)時(shí)的客房信息:");
Console.WriteLine(""); // 在此我也分別對(duì)3種不同渠道,采用3種不同的方式來(lái)實(shí)現(xiàn) // 其一、通過(guò)傳統(tǒng)的 new 方式來(lái)實(shí)例化一個(gè)task對(duì)象,獲取 攜程 的客房數(shù)據(jù)
Task<string> newCtripTask = new Task<string>(() =>
{ // 具體獲取業(yè)務(wù)邏輯處理...
Thread.Sleep(new Random().Next(100, 1000));
Console.WriteLine("攜程 內(nèi)部處理完畢!"); return "我是來(lái)自 攜程 的最新客房信息";
}); // 啟動(dòng) tsak newCtripTask.Start(); // 其二、通過(guò)工廠 factory 來(lái)生成一個(gè)task對(duì)象,并自啟動(dòng):獲取 藝龍 的客房數(shù)據(jù)
Task<string> factoryElongTask = Task<string>.Factory.StartNew(() =>
{ // 具體獲取業(yè)務(wù)邏輯處理...
Thread.Sleep(new Random().Next(100, 1000));
Console.WriteLine("藝龍 內(nèi)部處理完畢!"); return "我是來(lái)自 藝龍 的最新客房信息";
}); // 其三、通過(guò) Task.Run(Action action) 來(lái)創(chuàng)建一個(gè)自啟動(dòng)task:獲取 去哪兒網(wǎng) 的客房數(shù)據(jù)
Task<string> runQunarTask = Task<string>.Run(() =>
{ // 具體獲取業(yè)務(wù)邏輯處理...
Thread.Sleep(new Random().Next(100, 1000));
Console.WriteLine("去哪兒網(wǎng) 內(nèi)部處理完畢!"); return "我是來(lái)自 去哪兒網(wǎng) 的最新客房信息";
}); // 分別打印不同渠道的客房數(shù)據(jù) Console.WriteLine(newCtripTask.Result);
Console.WriteLine(factoryElongTask.Result);
Console.WriteLine(runQunarTask.Result);
Console.WriteLine("");
Console.WriteLine("所有接口方的最新客房數(shù)據(jù)獲取完畢!"); return listHotelRoomInfro;
}
執(zhí)行結(jié)果:
其實(shí)通過(guò)上面的執(zhí)行結(jié)果,我們不難看出以下幾點(diǎn) 1、task雖然是異步線程,但是可以有返回值的 2、task的返回值獲取方式為:task.Result 3、在通過(guò)task.Result獲取返回值時(shí),會(huì)阻塞主線程,其實(shí)也不難理解,你要等待處理結(jié)果,肯定會(huì)阻塞等待啦!
task可以同步執(zhí)行嗎?
通過(guò)上面的實(shí)際代碼測(cè)試,我們知道task都是異步執(zhí)行,那么有人會(huì)問,task可以實(shí)現(xiàn)同步執(zhí)行嗎?不急,強(qiáng)大的微軟也想到了這個(gè)問題,于是乎,提供了 task.RunSynchronously() 來(lái)同步執(zhí)行,但是這種方式執(zhí)行,只有通過(guò)new 實(shí)例化的task才有效,原因也很簡(jiǎn)單,其他兩種方式創(chuàng)建task都已經(jīng)自啟動(dòng)執(zhí)行了,不可能在來(lái)一個(gè)同步啟動(dòng)執(zhí)行吧,嘿嘿。下面我們用代碼來(lái)演示: /// <summary>
/// 通過(guò)RunSynchronously 實(shí)現(xiàn)task的同步執(zhí)行 /// </summary>
private static void TaskRunSynchronously()
{
Console.WriteLine("主線程開始執(zhí)行!");
Task<string> task = new Task<string>(() =>
{
Thread.Sleep(100);
Console.WriteLine("Task執(zhí)行結(jié)束!"); return "";
}); /// task.Start(); /// task.Wait();
// 獲取執(zhí)行結(jié)果,會(huì)阻塞主流程 // string result = task.Result; //同步執(zhí)行,task會(huì)阻塞主線程 task.RunSynchronously();
Console.WriteLine("執(zhí)行主線程結(jié)束!");
Console.ReadKey();
}
執(zhí)行結(jié)果:很明顯主線程阻塞等待task同步執(zhí)行。
task同步執(zhí)行,出了上面的實(shí)現(xiàn)方式,其實(shí)我們也可以通過(guò)task.wait()來(lái)變相的實(shí)現(xiàn)同步執(zhí)行效果,當(dāng)然也可以用task.Result來(lái)變現(xiàn)的實(shí)現(xiàn),原理很簡(jiǎn)單,因?yàn)閣ait()和Result都是要阻塞主流程,直到task執(zhí)行完畢,是不是有異曲同工之妙呢!以代碼為例: 通過(guò)task.wait()實(shí)現(xiàn),只需要對(duì)上面的代碼做一個(gè)簡(jiǎn)單的調(diào)整,如下:其最終的效果一樣: /// <summary>
/// 通過(guò)RunSynchronously 實(shí)現(xiàn)task的同步執(zhí)行 /// </summary>
private static void TaskRunSynchronously()
{
Console.WriteLine("主線程開始執(zhí)行!");
Task<string> task = new Task<string>(() =>
{
Thread.Sleep(100);
Console.WriteLine("Task執(zhí)行結(jié)束!"); return "";
});
task.Start();
task.Wait(); // 獲取執(zhí)行結(jié)果,會(huì)阻塞主流程 // string result = task.Result; // 同步執(zhí)行,task會(huì)阻塞主線程 // task.RunSynchronously();
Console.WriteLine("執(zhí)行主線程結(jié)束!");
Console.ReadKey();
}執(zhí)行結(jié)果:
通過(guò)task.Result 實(shí)現(xiàn),前提是task一定要有返回值,如下:其最終的效果一樣: /// <summary>
/// 通過(guò)RunSynchronously 實(shí)現(xiàn)task的同步執(zhí)行 /// </summary>
private static void TaskRunSynchronously()
{
Console.WriteLine("主線程開始執(zhí)行!");
Task<string> task = new Task<string>(() =>
{
Thread.Sleep(100);
Console.WriteLine("Task執(zhí)行結(jié)束!"); return "";
});
task.Start(); /// task.Wait();
// 獲取執(zhí)行結(jié)果,會(huì)阻塞主流程
string result = task.Result; // 同步執(zhí)行,task會(huì)阻塞主線程 // task.RunSynchronously();
Console.WriteLine("執(zhí)行主線程結(jié)束!");
Console.ReadKey();
}
執(zhí)行效果也和上面的兩種方式一樣。 當(dāng)然我還可以通過(guò)task.IsCompleted來(lái)變現(xiàn)實(shí)現(xiàn),在此就不在細(xì)說(shuō),簡(jiǎn)單一個(gè)代碼示意即可:while (!task.IsCompleted){} 當(dāng)然我上面說(shuō)的幾種實(shí)現(xiàn)同步的方式,只是為了拓展一下思路,不一定都是最優(yōu)方案。
Task的Wait、WaitAny、WaitAll方法介紹
task的基本創(chuàng)建和用法,上面都做了簡(jiǎn)單的介紹,但是在我們實(shí)際業(yè)務(wù)場(chǎng)景中,往往不是那么簡(jiǎn)單的單純實(shí)現(xiàn)。比如:還是剛剛上面的那個(gè)酒店信息獲取為例,現(xiàn)在新的需求是:3個(gè)渠道的接口實(shí)時(shí)數(shù)據(jù),我們只需要獲取到其中的一個(gè)就立即返回會(huì)用戶,避免用戶等待太久,那么這個(gè)時(shí)候task.WaitAny就派上用場(chǎng)了,WaitAny就是等待一組tsak集合中,只要有一個(gè)執(zhí)行完畢就不在等待,與之對(duì)應(yīng)的是WaitAll需要等待一組tsak集合中所有tsak都執(zhí)行完畢,當(dāng)然了Wait是針對(duì)一個(gè)task的,等待本身執(zhí)行完成,上面的模擬同步執(zhí)行已經(jīng)說(shuō)了,就不在啰嗦。 /// <summary>
/// 獲取最新的客房信息(只需要獲取到一個(gè)即可) /// </summary>
/// <returns>客房信息集合</returns>
private static List<string> GetOneHotelRoomInfro()
{ // 模擬存儲(chǔ)獲取到的酒店客房數(shù)據(jù)集合
List<string> listHotelRoomInfro = new List<string>();
Console.WriteLine("下面通過(guò)3個(gè)task,并行的到不同接口方獲取實(shí)時(shí)的客房信息:");
Console.WriteLine(""); // 在此我也分別對(duì)3種不同渠道,采用3種不同的方式來(lái)實(shí)現(xiàn) // 其一、通過(guò)傳統(tǒng)的 new 方式來(lái)實(shí)例化一個(gè)task對(duì)象,獲取 攜程 的客房數(shù)據(jù)
Task newCtripTask = new Task(() =>
{ // 具體獲取業(yè)務(wù)邏輯處理...
Thread.Sleep(new Random().Next(100, 1000));
listHotelRoomInfro.Add("我是來(lái)自 攜程 的最新客房信息");
}); // 啟動(dòng) tsak newCtripTask.Start(); // 其二、通過(guò)工廠 factory 來(lái)生成一個(gè)task對(duì)象,并自啟動(dòng):獲取 藝龍 的客房數(shù)據(jù)
Task factoryElongTask = Task.Factory.StartNew(() =>
{ // 具體獲取業(yè)務(wù)邏輯處理...
Thread.Sleep(new Random().Next(100, 1000));
listHotelRoomInfro.Add("我是來(lái)自 藝龍 的最新客房信息");
}); // 其三、通過(guò) Task.Run(Action action) 來(lái)創(chuàng)建一個(gè)自啟動(dòng)task:獲取 去哪兒網(wǎng) 的客房數(shù)據(jù)
Task runQunarTask = Task.Run(() =>
{ // 具體獲取業(yè)務(wù)邏輯處理...
Thread.Sleep(new Random().Next(100, 1000));
listHotelRoomInfro.Add("我是來(lái)自 去哪兒網(wǎng) 的最新客房信息");
}); // 只需要等待一個(gè)有返回即可
Task.WaitAny(new Task[] { newCtripTask, factoryElongTask, runQunarTask }); // 等待所有接口數(shù)據(jù)返回 // Task.WaitAll(new Task[] { newCtripTask, factoryElongTask, runQunarTask });
Console.WriteLine("已經(jīng)有接口數(shù)據(jù)返回!"); foreach (var item in listHotelRoomInfro)
{
Console.WriteLine($"返回的接口數(shù)據(jù)為:{item}");
}
Console.WriteLine("主線程執(zhí)行完畢!");
Console.ReadKey();
Console.WriteLine(""); return listHotelRoomInfro;
}
上面是演示了WaitAny的執(zhí)行結(jié)果,至于WaitAll就不在演示了,一看便知 好了,今天時(shí)間差不多了,就介紹到兒了,明天我們?cè)趤?lái)研究: 1、task的延續(xù)操作(WhenAny/WhenAll/ContinueWith) 2、任務(wù)取消(CancellationTokenSource) 3、實(shí)際案例分析 |
|
|