概述
在前面幾節中和大家分享了線程的一些基礎使用方法,本章結合之前的分享來編寫一些日常開發中應用實例,和編寫多線程時一些注意點。如大家有好的實例也歡迎分享..
應用實例
應用:定時任務程序
場景:系統中常常會有一些需要定時去循環執行的存儲過程或方法等,這時就出現了定時任務小程序。
模型:查詢需定時執行的計划任務-->插入線程池-->執行任務
static void MainMethod() { Thread thead; thead = new Thread(QueryTask); thead.IsBackground = true; thead.Start(); Console.Read(); } /// <summary> /// 查詢計划任務 /// </summary> static void QueryTask() { TaskModel todo_taskModel; while (true) { int count = new Random().Next(1, 10); //模擬產生任務記錄數 for (int i = 0; i < count; i++) { todo_taskModel = new TaskModel() { TaskID = i, ITime = DateTime.Now }; ThreadPool.QueueUserWorkItem(InvokeThreadMethod, todo_taskModel); } Thread.Sleep(30000); //30s循環一次 } } /// <summary> /// 完成計划任務 /// </summary> /// <param name="taskModel"></param> static void InvokeThreadMethod(object taskModel) { TaskModel model = (TaskModel)taskModel; Console.WriteLine("執行任務ID:{0}......執行完成.{1}", model.TaskID, model.ITime); } /// <summary> /// 任務實體 /// </summary> class TaskModel { public int TaskID { get; set; } public DateTime ITime { get; set; } }
1.查詢計划任務時只管有任務就推給線程池,不等待線程池中任務是否完成.(避免有些計划任務耗時比較長,阻塞后面定時任務執行時間)每隔30秒循環一次計划任務.
2.每條計划任務完成后打印相應信息.(也可記錄日志)
3.如果查詢計划任務記錄數較多,可調整相應的循環時間間隔。避免任務插入線程池時間過長阻塞后面定時任務執行時間。
應用:數據推送程序
場景:在我們日常系統中會存在很多接口數據需要實時推送給第三方平台(如:會員信息發生變化推送給微信平台..)。這時我們就需寫一些數據推送程序。
模型:檢索任務-->插入線程池-->等待線程池任務完成-->提示任務完成
static void MainMethodB() { Thread thread = new Thread(QueryTaskB); thread.IsBackground = true; thread.Start(); Console.Read(); } static void QueryTaskB() { MutipleThreadResetEvent countdown; TaskModeB model;
while (true) { int RandomNumber = new Random().Next(100, 200); countdown = new MutipleThreadResetEvent(RandomNumber); for (int i = 0; i < RandomNumber; i++) { model = new TaskModeB() { TaskId = i, ITime = DateTime.Now, manualResetEvent = countdown }; ThreadPool.QueueUserWorkItem(InvokeThreadMethodB, model); } //等待所有線程執行完畢 countdown.WaitAll(); Console.WriteLine("線程池任務已完成.完成時間:{0}", DateTime.Now); Thread.Sleep(30000); } } static void InvokeThreadMethodB(object obj) { TaskModeB model = (TaskModeB)obj; Thread.Sleep(1000); Console.WriteLine("執行任務ID:{0},執行時間:{1},完成時間:{2}", model.TaskId, model.ITime, DateTime.Now); //發送信號量 本線程執行完畢 model.manualResetEvent.SetOne(); } class TaskModeB { public int TaskId { set; get; } public DateTime ITime { set; get; } public MutipleThreadResetEvent manualResetEvent { set; get; } } /// <summary> /// 解決問題:WaitHandle.WaitAll(evetlist)方法最大只能等待64個ManualResetEvent事件 /// </summary> public class MutipleThreadResetEvent { private readonly ManualResetEvent done; private readonly int total; private long current; /// <summary> /// 構造函數 /// </summary> /// <param name="total">需要等待執行的線程總數</param> public MutipleThreadResetEvent(int total) { this.total = total; current = total; done = new ManualResetEvent(false); } /// <summary> /// 喚醒一個等待的線程 /// </summary> public void SetOne() { // Interlocked 原子操作類 ,此處將計數器減1 if (Interlocked.Decrement(ref current) == 0) { //當所以等待線程執行完畢時,喚醒等待的線程 done.Set(); } } /// <summary> /// 等待所以線程執行完畢 /// </summary> public void WaitAll() { done.WaitOne(); } /// <summary> /// 釋放對象占用的空間 /// </summary> public void Dispose() { ((IDisposable)done).Dispose(); } }
QueryTaskB()在檢索任務記錄數后會記錄任務條數,並實例化對應的ManualResetEvent數組,做為參數傳給線程池中線程任務。
最后等待線程池中所有任務執行完成。后續可根據實際需要編寫各自業務邏輯。
兩個實例的不同點:是否等待線程池中的所有任務完成。
實例1中定時任務對執行的時間要求比較高,到了某個時間點必須執行某個任務。所以不能等待線程池中的任務完成。
缺點:有任務就插入線程池,有些任務可能會執行很久,線程池每隔30秒循環一次,最后會導致線程池中有存在很多耗時很長的任務在線程池中未執行完。當線程池中線程數達到1023(線程池默認最大線程數)后線程池就不會在創建新的線程數去完成新的任務,只能等待當前線程池中線程數得到釋放。
實例2中數據推送程序對推送的時間要求相對1中要低一點。接口表中只要檢索到數據就推送給第三方平台。一般每次傳輸數據量不是很多,但很頻繁。
缺點:當推送數據量大時,執行任務時間可能會較長。主線程會等待線程池中的所有任務完成。所有每次循環檢索任務的時間間隔可能會出現30S+NS現象。
