在前面几节中和你们分享了线程的一些基础使用方法,本章结合以前的分享来编写一些平常开发中应用实例,和编写多线程时一些注意点。如你们有好的实例也欢迎分享..小程序
场景:系统中经常会有一些须要定时去循环执行的存储过程或方法等,这时就出现了定时任务小程序。数组
模型:查询需定时执行的计划任务-->插入线程池-->执行任务微信
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.每条计划任务完成后打印相应信息.(也可记录日志)dom
3.若是查询计划任务记录数较多,可调整相应的循环时间间隔。避免任务插入线程池时间过长阻塞后面定时任务执行时间。函数
场景:在咱们平常系统中会存在不少接口数据须要实时推送给第三方平台(如:会员信息发生变化推送给微信平台..)。这时咱们就需写一些数据推送程序。this
模型:检索任务-->插入线程池-->等待线程池任务完成-->提示任务完成spa
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现象。