在前面几节中和大家分享了线程的一些基础使用方法,本章结合之前的分享来编写一些日常开发中应用实例,和编写多线程时一些注意点。如大家有好的实例也欢迎分享..
场景:系统中常常会有一些需要定时去循环执行的存储过程或方法等,这时就出现了定时任务小程序。
模型:查询需定时执行的计划任务-->插入线程池-->执行任务
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现象。
新闻热点
疑难解答