2008-12-24 127 views
50

我有一个方法应该延迟运行一段指定的时间。比较使用Thread.Sleep和计时器延迟执行

我应该使用

Thread thread = new Thread(() => { 
    Thread.Sleep(millisecond); 
    action(); 
}); 
thread.IsBackground = true; 
thread.Start(); 

或者

Timer timer = new Timer(o => action(), null, millisecond, -1); 

我读了一些关于articles使用Thread.Sleep是糟糕的设计。但我不明白为什么。

但是对于使用Timer,Timer的配置方法。由于执行延迟,我不知道如何处理Timer。你有什么建议吗?

或者如果你有延迟执行的替代代码也很感激。

回答

39

一个区别是System.Threading.Timer在线程池线程上调度回调,而不是每次都创建一个新线程。如果您在应用程序的生命周期中需要这样的操作不止一次,这将节省创建和销毁一堆线程的开销(这是一个非常耗费资源的进程,正如您参考的文章指出的那样),因为它会只是重用池中的线程,并且如果您将同时运行多个计时器,这意味着您将有更少的线程同时运行(同时节省大量资源)。

换句话说,计时器将会更有效率。它也可能更准确,因为只要您指定的时间量(OS可能使其睡眠时间更长),Thread.Sleep只能保证至少等待。当然,Timer仍不会完全准确,但意图是尽可能接近指定的时间触发回调,而这不一定是Thread.Sleep的意图。

至于销毁定时器,回调可以接受一个参数,所以你可能能够传递Timer本身作为参数,并在回调中调用Dispose(虽然我没有试过这个 - 我想这是回调期间定时器可能被锁定)。

编辑:不,我想你不能这样做,因为你必须在指定Timer构造本身的回调参数。

也许这样? (再次,还没有真正尝试过)

class TimerState 
{ 
    public Timer Timer; 
} 

...并启动定时器:

TimerState state = new TimerState(); 

lock (state) 
{ 
    state.Timer = new Timer((callbackState) => { 
     action(); 
     lock (callbackState) { callbackState.Timer.Dispose(); } 
     }, state, millisecond, -1); 
} 

锁定应防止计时器回调从设法释放之前,定时器,定时器字段已被设置。


附录:正如评论者指出的那样,如果行动()做了与UI,然后使用一个System.Windows.Forms.Timer可能是一个更好的选择,因为它将运行在UI回调线。但是,如果情况并非如此,那么Thread.Sleep与Threading.Timer就是Threading.Timer。

+4

也值得指出与System.Windows.Forms.Timer的区别,我相信*在UI线程上调用一个函数,这对WinForms应用程序非常重要! – 2008-12-24 17:13:10

+2

对于未来的读者,“睡眠”并不是保证至少是事件,据记载它可能更少。 – 2013-04-01 14:04:19

+4

出于好奇......记录在哪里? – 2013-04-01 15:05:01

13

我觉得Thread.Sleep很好,如果你真的想暂停应用程序一段指定的时间。我认为人们说这是一个糟糕的设计的原因是因为在大多数情况下人们实际上并不希望应用程序暂停。

例如,我正在开发一个pop3客户端,程序员正在使用Thread.Sleep(1000)等待套接字检索邮件。在这种情况下,最好将一个事件处理程序连接到套接字并在套接字完成后继续执行程序。

+2

除了在后的两个例子是应用程序实际上暂停。海报正在单独的线程上暂停,而不是直接调用Thread.Sleep。直接调用Thread.Sleep,是的,坏主意。 – 2008-12-24 17:23:11

1

,我有与System.Timer唯一的牛肉是,大部分我所看到的时候它用于查询服务和开发人员长时间的延迟(小时,分)经常忘记启动事件他们开始之前定时器。这意味着如果我启动应用程序或服务,则必须等到定时器(小时,分钟)才真正执行。

当然,这不是计时器的问题,但我认为它经常使用不当,因为它太容易被误用。

16

使用ThreadPool.RegisterWaitForSingleObject代替定时器:

//Wait 5 seconds then print out to console. 
//You can replace AutoResetEvent with a Semaphore or EventWaitHandle if you want to execute the command on those events and/or the timeout 
System.Threading.ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), (state, bTimeout) => Console.WriteLine(state), "This is my state variable", TimeSpan.FromSeconds(5), true); 
0

@miniscalope没有不使用而不是定时器ThreadPool.RegisterWaitForSingleObject,System.Threading.Timer将排队等待回调到一个线程池线程中执行时,时间已过,不需要等待句柄,等待单个对象将绑定一个线程池线程,等待该事件发出信号或在线程调用回调之前超时超时。

2

我记得实施类似于Eric的解决方案。 然而,这是一个工作的;)

class OneTimer 
    { 
     // Created by Roy Feintuch 2009 
     // Basically we wrap a timer object in order to send itself as a context in order to dispose it after the cb invocation finished. This solves the problem of timer being GCed because going out of context 
     public static void DoOneTime(ThreadStart cb, TimeSpan dueTime) 
     { 
      var td = new TimerDisposer(); 
      var timer = new Timer(myTdToKill => 
      { 
       try 
       { 
        cb(); 
       } 
       catch (Exception ex) 
       { 
        Trace.WriteLine(string.Format("[DoOneTime] Error occured while invoking delegate. {0}", ex), "[OneTimer]"); 
       } 
       finally 
       { 
        ((TimerDisposer)myTdToKill).InternalTimer.Dispose(); 
       } 
      }, 
         td, dueTime, TimeSpan.FromMilliseconds(-1)); 

      td.InternalTimer = timer; 
     } 
    } 

    class TimerDisposer 
    { 
     public Timer InternalTimer { get; set; } 
    }