×

c# 线程定时器 System.Threading.Timer

admin admin 发表于2022-04-14 16:51:17 浏览3079 评论1

1人参与发表评论

System.Threading.Timer 是由线程池调用的。

所有的Timer对象只使用了一个线程来管理。这个线程知道下一个Timer对象在什么时候到期。下一个Timer对象到期时,线程就会唤醒,在内部调用ThreadPool 的 QueueUserWorkItem,将一个工作项添加到线程池队列中,使你的回调方法得到调用。如果回调方法的执行时间很长,计时器可能(在上个回调还没有完成的时候)再次触发。这可能造成多个线程池线程同时执行你的回调方法。


参数

callback : 一个Object 类型参数的委托,周期调用的函数。

state: callback 委托调用时的参数。

dueTime: 定时器延时多久开始调用。单位 毫秒

period: 定时器每隔多久调用一次。单位 毫秒


不能使用局部变量来创建指向一个线程定时器。因为局部变量会被GC回收,导致定时器失效。

比如下面的例子:


static void Main(string[] args) {


            Run();


            //为了加开GC垃圾回收,不停的创建对象

            for (int i = 0; i < 10000; i++) {

                cc tmp = new cc();

                Thread.Sleep(10);

            }


            Console.ReadKey();


        }


        static int id;


        static void Run() {

            Timer timer = new Timer(DoTime, null, 500, 500);

        }


        static void DoTime(object obj) {

            Console.WriteLine(id++);

        }


        class cc{ 

            public int[] a = new int[1000];

            }


定时器执行4次后停止了。定时器什么时候停止取决于GC什么时候回收。如果一直没有GC的回收,那么将会一直执行下去。比如把上方创建 cc 对象的代码删除。定时器将会一直执行。


可以在Main方法中使局部变量 或者 使用全局变量来存放Timer 对象 避免 clr 把Timer 回收。


比如改成这样


        static Timer timer;


        static void Main(string[] args) {


            timer =  new Timer(DoTime, null, 500, 500);


            //为了加开GC垃圾回收,不停的创建对象

            for (int i = 0; i < 10000; i++) {

                cc tmp = new cc();

                Thread.Sleep(10);

            }


            Console.ReadKey();


        }


        static int id;


        static void DoTime(object obj) {

            Console.WriteLine(id++);

        }


如果回调方法的执行时间很长,计时器可能(在上个回调还没有完成的时候)再次触发。这可能造成多个线程池线程同时执行你的回调方法。


    static Timer timer1;


        static void Main(string[] args) {


            timer1 = new Timer(DoTime, 1, 500, 500);



            Console.ReadKey();


        }


        static int id;


        static void DoTime(object obj) {

            int idx = id ++;

            Console.WriteLine("处理开始:" + idx + "," + Thread.CurrentThread.ManagedThreadId);

            Thread.Sleep(1200);

            Console.WriteLine("处理完毕:" + idx + "," + Thread.CurrentThread.ManagedThreadId);

        }


定时器第一次任务还没执行完毕,第二次,第三次回调就开始了。多个线程同时并发 DoTime 方法


解决方法

period 使用 Timeout.Infinite。这个参数将导致DoTime 只处理一次。

在回调方法中使用 Change方法来修改 dueTime,period 参数。period 继续使用 Timeout.Infinite. 使用这个方法要注意如果timer 在被Dispose了,使用Change 将会引发异常。

比如


 static Timer timer1;


        static void Main(string[] args) {


            timer1 = new Timer(DoTime, 1, 0, Timeout.Infinite);


            Console.ReadKey();


        }


        static int id;


        static void DoTime(object obj) {

            int idx = id ++;

            Console.WriteLine("处理开始:" + idx + "," + Thread.CurrentThread.ManagedThreadId);

            Thread.Sleep(1200);

            Console.WriteLine("处理完毕:" + idx + "," + Thread.CurrentThread.ManagedThreadId);

            timer1.Change(500,Timeout.Infinite);

        }


使用Disponse停止定时器

如果Timer 不会再使用 则可以 使用 Dispose 方法来停止定时器。

如果定时器运行到中途。使用Dispose方法后,callback还是会执行完一个完整的生命周期,不会中途停止。并且Dispose方法不会等待 callback的这次调用完成。只是定时器下次不再调用 callback。


使用Change停止定时器

把 dueTime 参数置为-1就可以停止定时器。同样的,它不会中断在运行中的callback,只是下一次不再回调。 这个方法停止的定时器 还可以使用Change 再次利用定时器。

————————————————

版权声明:本文为CSDN博主「驰愿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_27461747/article/details/105505420


群贤毕至

访客
admin admin2022-12-01 13:17:19 | 回复 xccvv