瞭解了多線程的基本使用後,現在我們來聊聊多線的實際項目場景:多線程的取與舍,也就是取在指定時間有響應結果的任務,捨棄未響應的任務。我們也遇到根據實際需要處理的業務量動態創建線程並控制等場景。 ...
hello task,咱們又見面啦!!前面已經通過三篇簡單的文章,對多線程的創建、運行、阻塞、等待、取消、延遲操作、非同步方法等相關的知識點,通過這一些介紹,現在上手寫一個多線程就是分分鐘的小事件。如果需要看前三排文章的小伙伴,可以點擊下麵鏈接快速閱讀謝謝!
說了那麼多後,我仔細想了一下,還是要來點實際的項目用例比較實在,那麼我現在就講我平時在項目中用常用的一些業務梳理處理,以供參考,寫到不好勿噴,有更好的解決方式,歡迎交流。謝謝!
應用一、多線程的中的取與舍
還是用上幾篇文章中的關於酒店客房的數據來為例分析,假設系統同時對接了x程、y龍、q哪三家介面數據,用戶進入到某一個酒店預訂頁面,系統需要實時的到第三方取該酒店對應房間的實時動態數據呈現給用戶,但是在這個過程中,不能讓用戶等待的太久,並且能夠儘可能多的提供多渠道給用戶選擇,那麼這個時候該如何去實現這個需求呢?
做過聚合平臺的同學,無論是酒店、機票、咨詢等等,或多或少都會遇到這樣類似的業務場景,下麵分享一下,我們平時是怎麼實現。
簡單的說就是一個取與舍的邏輯,你想啊,不同的介面方,介面的查詢效率不盡相同,並且同一介面在不同時間處理時間也不盡相同,遇到某一些特殊情況,有可能一個介面數據需要等待5-10s甚至更久才能取出數據,這個時候你不可能讓用戶也等待這麼久吧,如果是這樣,估計用戶早嚇跑了。況且這樣等待下去,也會對系統帶來很大的壓力,尤其是在做活動高峰期時,那直接就是系統的一個瓶頸了。
為了到達這一目的,那麼在實現上,我們首先應該想到的是,在約定時間內未返回數據的介面,那麼我們直接放棄,只展示給用戶已取到的數據即可。那在技術上該如何實現呢?
在實現上,有了Task,一切都變得那麼輕鬆啦,因為Task.Wait方法已經為我們考慮到了這樣的場景,直接使用即可。
我們看看Task.Wait的幾個重載吧:
看到了吧,在重載方法中,有一個timeOut欄位,該欄位就是用於捨棄超時未處理完成的線程任務。那具體該怎麼用呢?直接上代碼吧!
static void Main(string[] args) { Console.WriteLine("開始獲取酒店數據......"); // 獲取最新的客房信息(假設最大等待時間為1S) List<string> listHotelRoomInfro = GetHotelRoomInfro(1000); Console.WriteLine("獲取酒店數據獲取完畢,獲取到的數據為:"); foreach (var item in listHotelRoomInfro) { Console.WriteLine(item); } Console.ReadLine(); } /// <summary> /// 獲取最新的客房信息 /// </summary> /// <param name="maxWaitTimes">最大獲取時間(也就是捨棄時間),單位毫秒</param> /// <returns>客房信息集合</returns> private static List<string> GetHotelRoomInfro(int maxWaitTimes) { // 模擬存儲獲取到的酒店客房數據集合 List<string> listHotelRoomInfro = new List<string>(); // 在此我也分別對3種不同渠道,採用3種不同的方式來實現 // 其一、通過傳統的 new 方式來實例化一個task對象,獲取 攜程 的客房數據 Task newCtripTask = new Task(() => { // 具體獲取業務邏輯處理... Thread.Sleep(new Random().Next(100, 999)); listHotelRoomInfro.Add("我是來自 攜程 的最新客房信息"); }); // 啟動 tsak newCtripTask.Start(); // 其二、通過工廠 factory 來生成一個task對象,並自啟動:獲取 藝龍 的客房數據 Task factoryElongTask = Task.Factory.StartNew(() => { // 具體獲取業務邏輯處理... Thread.Sleep(new Random().Next(555, 1500)); listHotelRoomInfro.Add("我是來自 藝龍 的最新客房信息"); }); // 其三、通過 Task.Run(Action action) 來創建一個自啟動task:獲取 去哪兒網 的客房數據 Task runQunarTask = Task.Run(() => { // 具體獲取業務邏輯處理... Thread.Sleep(new Random().Next(1100, 2000)); listHotelRoomInfro.Add("我是來自 去哪兒網 的最新客房信息"); }); // 等待獲取介面,阻塞主流程,如果 在 指定的時間 maxWaitTimes未返回數據的介面方直接捨棄掉 Task.WaitAll(new Task[] { newCtripTask, factoryElongTask, runQunarTask }, maxWaitTimes); return listHotelRoomInfro; }
運行結果:
通過上面的運行結果,我們發現在1s內,只有攜程返回了數據,那麼最終也就只返回給用戶攜程的數據。這個實例就這樣實現了,當然,wi相信你或許會有更好的實現,歡迎一起交流與學習,謝謝
應用二:動態創建多線程均批處理
在實際項目中,我們會遇到需要根據待處理任務數量,動態創建多線程來最優化分批處理。哈哈哈是不是說的有點空洞,雲里霧裡的,不急不急,下麵來一個實際場景,一看你就明明白白啦。
需求:還是以酒店數據同步為例。具體需求是這樣的:
1.手動批選擇指定酒店,單次最多可選擇500個
2.系統需要以最快的方式同步會介面方的最新數據
哈哈哈看了需求是不是覺得很簡單,就兩句話,那該如何來實現這個需求呢?
也許你會說,簡單啊,根據所選擇的酒店以次排隊同步數據就對了嘛!這樣可不行哦,如果用戶選擇了500個酒店,假設每個酒店數據同步需要2秒,那麼500條數據都需要1000秒,那需要接近20分鐘才處理完,這可不是時間最優啊!
那你也要你還會說那就直接每一個酒店都開一個線程來處理,這樣速度是不是夠快啦!嗯你說的沒錯,這樣速度是上去了,但是呢,如果用戶選擇500個酒店,那麼就需要創建500個線程,也就是500個併發,這樣會有什麼問題呢?第一.你自己的伺服器扛的住嗎?第二.如果你伺服器扛的住,那麼介面允許你這麼幹嘛?估計早都把你拉黑名單了。所以這也不是最優方案。
哈哈,說了那麼多,那麼具體該如何來實現呢?其實也簡單,我想說的方案就去以上兩兩種方案的一個折中方案。動態的根據資源等多因素動態創建合理的線程,然後在並行處理。來來,直接上代碼!
class Program { /// <summary> /// 最多允許創建的線程數,可以通過配置文件來配置 /// 具體的值也該是由:伺服器資源+介面限制等多因數來確定的 /// </summary> public static int maxThread = 10; static void Main(string[] args) { // 假設選擇處理20條酒店數據 List<string> listHotel = new List<string>(); for (int i = 0; i < 20; i++) { listHotel.Add($"我是酒店{(i + 1).ToString().PadLeft(2, '0')}"); } // 創建Tsak處理酒店數據同步 AsyncDynamicSynchronouslyHotel(listHotel); Console.ReadLine(); } /// <summary> /// 根據酒店數據量,動態創建Tsak來處理酒店數據同步 /// </summary> /// <param name="listHotel">待處理的酒店數據列表</param> private static void AsyncDynamicSynchronouslyHotel(List<string> listHotel) { object hotelTaskLock = new object(); // 先做一個非空判斷 if (listHotel == null || listHotel.Count < 1) { return; } // task線程數組 List<Task> taskList = new List<Task>(); // 首先根據資源數據量+最大允許線程數來確定需要開啟的線程 int taskCount = listHotel.Count < maxThread ? listHotel.Count : maxThread; // 創建指定是task線程數 for (int i = 0; i < taskCount; i++) { // 創建一個task線程 taskList.Add(new Task(() => { while (listHotel != null && listHotel.Count > 0) { // 給該線程分配一個酒店處理任務 string hotelInfro = string.Empty; // 線程通過,加一個資源鎖 lock (hotelTaskLock) { // 在獲取到鎖後,還需要做一個資源判斷,避免獲取到鎖後,資源以及被消耗完畢 if (listHotel != null && listHotel.Count > 0) { hotelInfro = listHotel[0]; listHotel.Remove(hotelInfro); } } // 開始模擬真正的數據同步操作 if (!string.IsNullOrEmpty(hotelInfro)) { Thread.Sleep(1000); Console.WriteLine($"我是線程ID{Thread.CurrentThread.ManagedThreadId.ToString() },完成酒店【{hotelInfro}的數據同步處理"); } } })); // 啟動線程 taskList[i].Start(); } } }
運行結果:
設置最多開5個線程
設置對多開10個線程
這樣就達到了自動的動態控制線程創建,當然這個裡面還涉及到了線程同步等問題處理
總結:
通過本篇文章,和大家分享了多線程的取與舍,多線的動態創建等等,在實際工作過程中,或多或多我們都會遇到這樣的場景,希望能夠有點幫助
好了,今天就說在這兒了,有什麼問題,大家可以多多交流,最後祝大家元旦快樂
猜您喜歡:
第一篇:聊聊多線程哪一些事兒(task)之 一創建運行與阻塞
第三篇:聊聊多線程那一些事兒(task)之 三 非同步取消和非同步方法
第四篇:聊聊多線程那一些事兒 之 四 經典應用(取與舍、動態創建)
END
為了更高的交流,歡迎大家關註我的公眾號,掃描下麵二維碼即可關註,謝謝: