【问题标题】:Compare using Thread.Sleep and Timer for delayed execution比较使用 Thread.Sleep 和 Timer 来延迟执行
【发布时间】:2010-09-28 08:14:35
【问题描述】:

我有一个方法应该延迟运行一段指定的时间。

我应该使用

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

或者

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

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

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

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

【问题讨论】:

    标签: c# multithreading timer sleep delayed-execution


    【解决方案1】:

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

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

    【讨论】:

    • 除了帖子中的两个示例之外,应用程序实际上都在暂停。海报在单独的线程上暂停,而不是直接调用 Thread.Sleep 。直接调用 Thread.Sleep,是的,坏主意。
    【解决方案2】:

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

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

    至于销毁Timer,回调可以接受一个参数,所以你也许可以将Timer本身作为参数传递并在回调中调用Dispose(虽然我没有尝试过这个——我猜测可能是在回调期间 Timer 可能被锁定)。

    编辑:不,我想你不能这样做,因为你必须在 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);
    }
    

    锁定应防止计时器回调在设置Timer 字段之前尝试释放计时器。


    附录:正如评论者所指出的,如果action() 对 UI 进行了处理,那么使用System.Windows.Forms.Timer 可能是更好的选择,因为它将在 UI 线程上运行回调。但是,如果不是这种情况,并且取决于 Thread.SleepThreading.TimerThreading.Timer 是要走的路。

    【讨论】:

    • 还值得指出与 System.Windows.Forms.Timer 的区别,我相信在 UI 线程上调用一个函数,这对于 WinForms 应用程序非常重要!
    • 对于未来的读者,Sleep 不能保证至少是事件,据记录它可能会更少。
    • 出于好奇...记录在哪里?
    • 我试过了,5分钟后收到了内存不足的消息
    • 睡眠函数”文档说:“这个函数导致线程放弃其时间片的剩余部分,并在基于值的间隔内变得不可运行dwMilliseconds。系统时钟以恒定速率“滴答”。如果 dwMilliseconds 小于系统时钟的分辨率,则线程可能会休眠少于指定的时间长度。如果 dwMilliseconds 大于一个滴答但小于两个, 等待可以是一到两个滴答声之间的任何时间,依此类推。"
    【解决方案3】:

    我对 System.Timer 的唯一不满是,在大多数情况下,我看到它在轮询服务中用于长时间延迟(小时、分钟),并且开发人员经常忘记启动事件 之前 em> 他们启动计时器。这意味着如果我启动应用程序或服务,我必须等到计时器过去(小时、分钟)才能真正执行。

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

    【讨论】:

      【解决方案4】:

      使用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);
      

      【讨论】:

      • 阅读接受的答案。定时器已使用线程池。
      • 我更喜欢 RegisterWaitForSingleObject 的逻辑......该方法在时间到时执行一次......所以一旦工作完成,你就不必欺骗停止计时器,这是不好的。 .. 所以 RegisterWaitForSingleObject 自然地做他想做的事,当你想以特定的时间间隔多次执行任务时,计时器不要计时器更好......
      • 我添加了一个例子。此问题stackoverflow.com/questions/1535262/… 提供更多详细信息
      • 我同意,这是我更喜欢的方法。我写的以下帖子支持这个答案,并详细说明了那些想知道如何使用 RegisterWaitForSingleObject 的人的一些用途:allen-conway-dotnet.blogspot.com/2009/12/…
      【解决方案5】:

      @miniscalope 不,不要使用 ThreadPool.RegisterWaitForSingleObject 代替计时器,System.Threading.Timer 将在时间过去并且不需要等待句柄时将回调排队在线程池线程上执行,等待单个对象将占用一个线程池线程,等待事件发出信号或超时到期,然后线程调用回调。

      【讨论】:

      • 你有这方面的文档吗?你本质上是在说ThreadPool.RegisterWaitForSingleObjectThread.Sleep 一样工作吗?通过声明:“等待单个对象将占用线程池线程,等待事件发出信号或超时在线程调用回调之前到期”我>?
      • 它不使用 Thread.Sleep... 在挖掘源代码后它暗示它调用了本机 RegisterWaitForSingleObject (我说暗示是因为它调用的方法是外部的,但装饰为内部运行时调用...)如果该假设是正确的,您可以假设(来自docs)它将使用本机线程池中的等待线程来等待对象发出信号,然后在工作线程上执行回调。
      【解决方案6】:

      我记得实施了一个类似于 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; }
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-07-22
        • 1970-01-01
        • 1970-01-01
        • 2010-11-09
        • 2019-04-16
        • 1970-01-01
        • 2012-10-06
        相关资源
        最近更新 更多