此文并不是說要完全放棄使用Thread.Sleep,而是要說明在符合哪些情況下使用!
很多時候,我們會需要一個定時服務來處理業務。
但并不是死死的每隔N分鐘執行一次那種,而是在一次處理完后,算好下一次處理的時間點。
當到達此時間點,觸發程序重新開始執行代碼。
普遍的情況下,都是使用while(true){Thread.Sleep()}來實現,廢話不多話,看代碼版本1:
class Program{ static void Main(string[] args) { var workLists = new List<string>() { "任務1", "任務2", "任務3", "任務4" }; foreach (var task in workLists) { var thread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(Work.DoWork)); thread.Start(task); } }}
class Work{ public static void DoWork(object target) { var taskType = target as string; var interval = 1 * 60 * 1000;//處理失敗,1分鐘后重試 var maxTimes = 5; var retryTimes = 0; while (true) { while (retryTimes < maxTimes) { var ok = Proccess(taskType); if (ok) { retryTimes = maxTimes; } else { retryTimes++; System.Threading.Thread.Sleep(interval); } } var tim = GetTotalMillisecondsForNext();//計算離下一次開始處理的時間 System.Threading.Thread.Sleep(tim);//掛起一段時間后,重新喚醒 retryTimes = 0; } } private static bool Proccess(string taskType) { Console.WriteLine("開始執行處理:{0}", taskType); return true; } private static int GetTotalMillisecondsForNext() { //這里根據自己的業務來決定 return 2 * 1000; }}
代碼簡單易懂。
版本1中,循環強制創建線程,并使用System.Threading.Thread.Sleep(tim)來掛起線程,然后重新喚醒。
這種方式不好之處在于:占用系統線程資源,是一種浪費。如同占著茅坑不拉屎!線程是一種十分寶貴的資源,創建,銷毀,切換 都是相當耗性能的。
當Sleep的時候,就等于說:現在我不用,但是你也別想用。你要用?自己去Create一個。
有的人說,Sleep的時候 不占用CPU啊!對,是不占用CPU ,但是占著線程資源,阻礙系統的線程調度!
可以參考下 這文章
Threads are a limited resource, they take approximately 200,000 cycles to create and about 100,000 cycles to destroy. By default they reserve 1 megabyte of virtual memory for its stack and use 2,000-8,000 cycles for each context switch. This makes any waiting thread a huge waste.
使用System.Timers.Timer來改進我們的程序。當執行處理業務的代碼時,首先把timer停止,處理完畢后,算好一次執行的時間點,賦給timer并啟動,看代碼版本2
class Program{ static void Main(string[] args) { var workLists = new List<string>() { "任務1", "任務2", "任務3", "任務4" }; Parallel.ForEach(workLists, new ParallelOptions() { MaxDegreeOfParallelism = 3 }, (task) => { new Work2() { TaskType = task }.DoWork(); }); Console.ReadLine(); }}
class Work2 { private Timer _workTimer; public string TaskType { get; set; } public void DoWork() { _workTimer = new System.Timers.Timer(); _workTimer.Interval = 1000; _workTimer.Elapsed += new ElapsedEventHandler(TimerHanlder); _workTimer.Start(); } private void TimerHanlder(object sender, ElapsedEventArgs e) { _workTimer.Stop(); var interval = 1 * 60 * 1000;//處理失敗,1分鐘后重試 var maxTimes = 5; var retryTimes = 0; while (retryTimes < maxTimes) { var ok = Proccess(); if (ok) { retryTimes = maxTimes; } else { retryTimes++; System.Threading.Thread.Sleep(interval); } } var times = GetTotalSecondsForNext(); Console.WriteLine("{0}秒后重新執行", times); _workTimer.Interval = times * 1000;//計算離下一次開始處理的時間 _workTimer.Start(); } private bool Proccess() { Console.WriteLine("開始執行處理:{0}", TaskType); return true; } private int GetTotalSecondsForNext() { //這里根據自己的業務來決定 return 3; } }
特別說明一下:Main方法中的Console.ReadLine();很重要,讓主線程處于等待的狀態,子線程就可以一直執行下去不中斷
1:使用Task,而不是使用new System.Threading.Thread。是否要創建線程,應該讓系統來決定,利用可復用資源
2: System.Threading.Thread.Sleep(interval);只合適在 "有限度的 " 循環場景中,比如 最多重試N次、倒計時等等
新聞熱點
疑難解答
圖片精選