前言
此文并不是说要完全放弃使用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次、倒计时等等
如果不对之处,请各位斧正!