1、 Timer种类
1.System.Threading.Timer
2.System.Timers.Timer
3.System.Windows.Forms.Timer
4.System.Windows.Threading.DispatcherTimer
2、 Timer用法及简介
A.System.Threading.Timer
提供以指定的时间间隔对线程池线程执行方法的机制。 无法继承此类。
主要有四个参数。
CallBack,一个返回值为void,参数为object的委托,也是计时器执行的方法。
state,计时器执行方法的的参数。
dueTime,调用 callback 之前延迟的时间量(以毫秒为单位)。
period,调用 callback 的时间间隔。
例:System.Threading.Timer tm=new System.Threading.Timer(tick,null,1000,2000);//1秒后开始计时,2秒后调用tick事件。
特点:多线程计时器,精确,而且可扩展性强。
B. System.Timers.Timer
MSDN说明:
Timer 组件是基于服务器的计时器,它使您能够指定在应用程序中引发 Elapsed 事件的周期性间隔。然后可以操控此事件以提供定期处理。例如,假设您有一台关键性服务器,必须每周 7 天、每天 24 小时都保持运行。可以创建一个使用 Timer 的服务,以定期检查服务器并确保系统开启并在运行。如果系统不响应,则该服务可以尝试重新启动服务器或通知管理员。
基于服务器的 Timer 是为在多线程环境中用于辅助线程而设计的。服务器计时器可以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows 计时器更精确地按时引发事件。有关基于服务器的计时器的更多信息,请参见“基于服务器的计时器介绍”。
基于 Interval 属性的值,Timer 组件引发 Elapsed 事件。可以处理该事件以执行所需的处理。例如,假设您有一个联机销售应用程序,它不断向数据库发送销售订单。编译发货指令的服务分批处理订单,而不是分别处理每个订单。可以使用 Timer 每 30 分钟启动一次批处理。
Timer.Elapsed
事件达到间隔时发生。如果将 Enabled 设置为 true 并将 AutoReset 设置为 false,则 Timer 在第一次达到间隔时仅引发一次 Elapsed 事件。如果在已启动 Timer 后设置 Interval,则重置计数。例如,如果将间隔设置为 5 秒,然后将 Enabled 设置为 true,则计数将在设置 Enabled 时开始。如果在计数为 2 秒时将间隔重置为 10 秒,则 Elapsed 事件在 Enabled 设置为 true 的 12 秒之后第一次引发。
Elapsed 事件在 ThreadPool 线程上引发。如果 Elapsed 事件的处理时间比 Interval 长,在另一个 ThreadPool 线程中将会再次引发此事件。因此,事件处理程序应当是可重入的。
Eg:
public static void Test() { System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(OnTimerdEnt); timer.Interval = 50;//50ms timer.Enabled = true;//cycle
GC.KeepAlive(timer); }
private static void OnTimerdEnt(object obj, ElapsedEventArgs e) { Console.WriteLine("Hello World!"); }
|
注意 |
Elapsed 事件在 ThreadPool 线程上引发,因此事件处理方法可以运行在一个线程上,同时 Stop 方法调用运行在另一个线程上。这可能导致在调用 Stop 方法后引发 Elapsed 事件。此主题的代码示例演示了一种防止争用条件的方法。 |
下面的代码示例演示了一种防止调用 Stop 方法的线程在当前正在执行的 Elapsed 事件结束之前继续进行,以及防止两个 Elapsed 事件同时执行事件处理程序(通常称为可重入性)的方法。
此示例执行 100 次测试运行。每次运行测试时,计时器以 150 毫秒的间隔启动。事件处理程序使用 Thread.Sleep 方法来模拟一个长度在 50 和 200 毫秒之间随机变化的任务。测试方法还启动一个控制线程,此线程等待一秒然后停止计时器。如果此控制线程停止计时器时有一个事件正被处理,则此线程必须等待此事件完成后才能继续进行。
Interlocked.CompareExchange(Int32,Int32,Int32) 方法(原子操作)重写用于避免可重入性和防止控制线程在正在执行的事件结束前继续进行。事件处理程序使用 CompareExchange(Int32,Int32,Int32) 方法将一个控制变量设置为 1,但仅在此变量的当前值为零时才进行此设置。如果返回值为零,则此控制变量已被设置为 1 并且事件处理程序继续进行。如果返回值不为零,则只是丢弃此事件以避免可重入性。(如果有必要执行每一个事件,Monitor 类将是一个同步这些事件的更好方法。)事件处理程序在结束时将控制变量设置回零。此示例记录已执行的事件、由于可重入性而被丢弃的事件以及在调用 Stop 方法后发生的事件的总数。
控制线程使用 CompareExchange(Int32,Int32,Int32) 方法将控制变量设置为 -1(负一),但仅在此变量的当前值为零时才进行此设置。如果原子操作返回非零值,则当前有一个事件正在执行。控制线程等待并重试。此示例记录控制线程等待一个事件完成的次数。
Eg:
private static int testRuns = 100; private static int testRunsFor = 1000; private static int timerInterval = 150; private static System.Timers.Timer Timer1 = new System.Timers.Timer(); private static Random rand = new Random(); private static int syncPoint = 0; private static int numEvents = 0; private static int numExecuted = 0; private static int numSkipped = 0; private static int numLate = 0; private static int numWaits = 0; [MTAThread] public static void Main() { Timer1.Elapsed += new ElapsedEventHandler(Timer1_ElapsedEventHandler); Timer1.Interval = timerInterval; Console.WriteLine(); for(int i = 1; i <= testRuns; i++) { TestRun(); Console.Write("\rTest {0}/{1} ", i, testRuns); } Console.WriteLine("{0} test runs completed.", testRuns); Console.WriteLine("{0} events were raised.", numEvents); Console.WriteLine("{0} events executed.", numExecuted); Console.WriteLine("{0} events were skipped for concurrency.", numSkipped); Console.WriteLine("{0} events were skipped because they were late.", numLate); Console.WriteLine("Control thread waited {0} times for an event to complete.", numWaits); } public static void TestRun() { syncPoint = 0; Timer1.Enabled = true; Thread t = new Thread(ControlThreadProc); t.Start(); t.Join(); } private static void ControlThreadProc() { Thread.Sleep(testRunsFor); Timer1.Stop(); bool counted = false; while (Interlocked.CompareExchange(ref syncPoint, -1, 0) != 0) { Thread.Sleep(0); if (!counted) { numWaits += 1; counted = true; } } } private static void Timer1_ElapsedEventHandler( object sender, ElapsedEventArgs e) { numEvents += 1; // int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0); if (sync == 0) { int delay = 50 + rand.Next(150); Thread.Sleep(delay); numExecuted += 1; syncPoint = 0; } else { if (sync == 1) { numSkipped += 1; } else { numLate += 1; } } }
|
小结:
上述两个Timer都是多线程计时器。Timer每到间隔时间后就会激发响应事件,因此要申请线程来执行对应的响应函数,Timer将获取线程的工作都交给了线程池来管理,
每到一定的时间后它就去告诉线程池:“我现在激发了个事件要运行对应的响应函数,麻烦你给我向操作系统要个线程,申请交给你了,线程分配下来了你就运行我给你的响应函数,
没分配下来先让响应函数在这儿排队(操作系统线程等待队列)”,消息已经传递给线程池了,Timer也就不管了,因为它还有其他的事要做(每隔一段时间它又要激发事件),
至于提交的请求什么时候能够得到满足,要看线程池当前的状态,如果线程满了就排队,在有空线程时就会响应函数。否则就直接响应。
C. System.Windows.Forms.Timer
实现按用户定义的时间间隔引发事件的计时器。 此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用,单线程使用。由于单线程计时器基于Windows消息循环,应用程序会同步的处理计时器的消息。UI界面会相对响应速度很慢。此 Windows 计时器专为使用 UI 线程来执行处理的单线程的环境。 它要求用户代码有一个可用的用户界面消息泵,并且始终在同一个线程操作或到另一个线程的调用封送。
D.System.Windows.Threading.DispatcherTimer
集成到Dispatcher队列的计时器,在指定时间间隔和指定的优先级处理。
小结:
两个是单线程计时器,其工作机制也和上面不同。计时器使用消息循环机制来取代线程池产生消息的机制。
这意味着Tick事件总是在创建timer的那个线程上执行,同时也意味着如果上一个Tick消息还未被处理,即使时间超过了间隔时间,在消息循环中也只存在一个Tick消息。
它们都是线程安全。一个Tick事件在前一个Tick事件被处理完毕前不会被触发。你可以直接在Tick事件处理代码中更新控件,不需要调用Control.Invoke或Dispatcher.Invoke.
文中有多处借鉴