【问题标题】:Alternatives to Thread.Sleep()Thread.Sleep() 的替代方案
【发布时间】:2011-03-24 19:47:59
【问题描述】:

我们希望每 N 分钟运行一次任务列表。所以我们创建了一个带有

的任务执行器
do { DoWork(); }while(!stopRequested)

现在我们想要在工作周期之间暂停一下。每个人似乎都认为 Thread.Sleep() 是魔鬼。我见过提到使用监视器/事件的东西,但我们没有其他人告诉我们做工作。我们只想每 N 分钟做一次,就像发条一样。

那么是否有替代方法或者我找到了 Thread.Sleep 的有效用法?

另一篇文章中有人提到 WaitHandle.WaitOne() 作为替代方案,但您显然不能从非静态方法调用它?或者至少我不能因为我得到一个编译时错误..

需要一个对象引用 非静态字段、方法或 财产 'System.Threading.WaitHandle.WaitOne(System.TimeSpan)'

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

    据我了解,Thread.Sleep() 很糟糕,因为它会强制线程的资源从缓存中取出,因此之后必须再次加载它们。没什么大不了的,但它可能会加剧高负载情况下的性能问题。还有一个事实是时间不准确,它实际上不能等待大约 10 毫秒以下的持续时间......

    我用这个sn-p:

    new System.Threading.ManualResetEvent(false).WaitOne(1000);
    

    简单易行,所有内容都放在一条线上。创建一个永远不会设置的新事件处理程序,然后等待完整的超时时间,您将其指定为WaitOne() 的参数。

    尽管对于这种特定情况,Timer 可能是更合适的方法:

    var workTimer = new System.Threading.Timer(
        (x) => DoWork(),
        null,
        1000, // initial wait period
        300000); // subsequent wait period
    

    然后,您可以使用workTimer.Stop() 停止计时器,而不是设置取消变量。


    编辑

    由于人们仍然觉得这很有用,我应该补充一点,.NET 4.5 引入了Task.Delay 方法,它更加简洁并且还支持异步:

    Task.Delay(2000).Wait(); // Wait 2 seconds with blocking
    await Task.Delay(2000); // Wait 2 seconds without blocking
    

    【讨论】:

    • 在寻找 thread.sleep 的替代品时,我发现您的 1 行 sn-p 非常适合我自己,我只想暂停一两秒,每次都能登录到控制台这不适用于 thread.sleep。
    • 我注意到 ManualResetEventIDisposable,但您的代码让匿名实例改为 GC。在这种情况下,不明确调用Dispose 有什么风险吗?
    • 好问题。从我在反汇编中可以看到,ManualResetEvent 使用基本的WaitHandle 功能,其 Dispose 方法只是处理其内部的SafeWaitHandle 实例。 SafeWaitHandle 的基类 SafeHandle 无论如何都会在其解构函数中显式调用其自己的 Dispose 方法,因此我假设任何非托管操作系统句柄都会在对象被 GC 时被清除。但 2016 年最好的选择是只使用 Task.Delay 并避免整个问题。 :)
    【解决方案2】:

    当然,您必须致电WaitOne on WaitHandle。这是一个实例方法。否则它怎么知道等待什么?

    拥有可以做出反应的东西绝对比睡眠更好,这样您就可以在无需等待几分钟的情况下无缘无故地注意到取消。 WaitHandle 的另一种替代方法是使用 Monitor.Wait/Pulse

    但是,如果您使用的是 .NET 4,我会研究一下 Task Parallel Library 必须提供什么...它比其他选项略高,并且通常是一个经过深思熟虑的库。

    对于常规工作任务,您可能希望使用TimerSystem.Threading.TimerSystem.Timers.Timer)甚至Quartz.NET

    【讨论】:

    • 你认为这个答案还有效吗?
    • @Cer:它应该仍然可以工作,当然。另一种方法是使用带有取消令牌的任务。
    【解决方案3】:

    Thread.Sleep 不是魔鬼——你可以在这样的场景中使用它。只是短期内不太可靠。

    使用 WaitHandle 是一个不错的选择 - 但您需要一个特定的等待句柄实例。然而,它不会单独做到这一点。

    话虽如此,大多数时候,像这样的操作更适合使用 Timer。

    【讨论】:

    • 不,Thread.Sleep 不是魔鬼。它更像是光不足王子菲尔 (en.wikipedia.org/wiki/…)。不过说真的,Thread.Sleep 的好用处相对较少。对我来说,这是代码中的危险信号。大多数情况下,将Sleep 替换为计时器会产生更清晰、更健壮的代码。
    【解决方案4】:

    我能想到的三个选项是:

    但我相信很多人都会提到 - Thread.Sleep() 并不是那么糟糕。

    【讨论】:

      【解决方案5】:

      您可以使用您设置的 ManualResetEvent 来停止,然后对其执行 WaitOne。

      【讨论】:

        【解决方案6】:

        我会使用一个等待计时器来发出 AutoResetEvent 信号。您的线程应该等待这个 WaitHandle 对象。这是一个展示这种方法的小型控制台应用程序:

        class Program {
            const int TimerPeriod = 5;
            static System.Threading.Timer timer;
            static AutoResetEvent ev;
            static void Main(string[] args) 
            {
                ThreadStart start = new ThreadStart(SomeThreadMethod);
                Thread thr = new Thread(start);
                thr.Name = "background";
                thr.IsBackground = true;
                ev = new AutoResetEvent(false);
                timer = new System.Threading.Timer(
                    Timer_TimerCallback, ev, TimeSpan.FromSeconds(TimerPeriod), TimeSpan.Zero);
                thr.Start();
                Console.WriteLine(string.Format("Timer started at {0}", DateTime.Now));
                Console.ReadLine();
            }
        
            static void Timer_TimerCallback(object state) {
                AutoResetEvent ev =  state as AutoResetEvent;
                Console.WriteLine(string.Format
                     ("Timer's callback method is executed at {0}, Thread: ", 
                     new object[] { DateTime.Now, Thread.CurrentThread.Name}));
                ev.Set();
            }
        
            static void SomeThreadMethod() {
                WaitHandle.WaitAll(new WaitHandle[] { ev });
                Console.WriteLine(string.Format("Thread is running at {0}", DateTime.Now));
            }
        }
        

        【讨论】:

          【解决方案7】:

          如果你所有的线程都在做这样的事情:

          while (!stop_working)
          {
              DoWork();
              Thread.Sleep(FiveMinutes);
          }
          

          那么我建议根本不要使用线程。首先,没有什么特别好的理由来承担大部分时间都在休眠的专用线程的系统开销。其次,如果您在线程停止休眠 30 秒后设置 stop_working 标志,则您必须等待四分半钟才能唤醒并终止线程。

          我建议像其他人一样:使用计时器:

          System.Threading.Timer WorkTimer;
          
          // Somewhere in your initialization:
          
          WorkTimer = new System.Threading.Timer((state) =>
              {
                  DoWork();
              }, null, TimeSpan.FromMinutes(5.0), TimeSpan.FromMinutes(5.0));
          

          然后关闭:

           WorkTimer.Dispose();
          

          【讨论】:

            【解决方案8】:

            您可以使用 System.Timers.Timer 并在其 elapse 处理程序中执行工作。

            【讨论】:

              【解决方案9】:

              正如其他回答者所说,计时器可能对您很有效。

              如果您确实想要自己的线程,我不会在这里使用 Thread.Sleep,如果只是因为如果您需要关闭应用程序,没有好的方法告诉它退出睡眠。我以前用过这样的东西。

              class IntervalWorker
              {
                  Thread workerThread;
                  ManualResetEventSlim exitHandle = new ManualResetEventSlim();
              
                  public IntervalWorker()
                  {
                      this.workerThread = new Thread(this.WorkerThread);
                      this.workerThread.Priority = ThreadPriority.Lowest;
                      this.workerThread.IsBackground = true;
                  }
              
                  public void Start()
                  {
                      this.workerThread.Start();
                  }
              
                  public void Stop()
                  {
                      this.exitHandle.Set();
                      this.workerThread.Join();
                  }
              
                  private void WorkerThread()
                  {
                      int waitTimeMillis = 10000; // first work 10 seconds after startup.
              
                      while (!exitHandle.Wait(waitTimeMillis))
                      {
                          DoWork();
              
                          waitTimeMillis = 300000; // subsequent work at five minute intervals.
                      }
                  }
              }
              

              【讨论】:

                【解决方案10】:

                我在 WinForms 应用程序和控制台应用程序中都使用了 Timer,它由服务器上的 ScheduledTask 定期启动。带有计时器的 WinForms 已用于我希望它弹出它运行的通知以及它找到的通知(我已将它们设置为最小化到系统托盘)的情况。对于我只想在服务器出现问题时收到通知的情况,我将它们作为控制台应用程序放在服务器上并作为计划任务运行它们。这似乎工作得很好。

                我想我尝试在现在使用计时器的应用程序中使用睡眠,但结果不喜欢。一方面,使用计时器,我可以很容易地调用最小化的应用程序,以便在前面设置或更改设置。如果应用处于睡眠状态,您将难以在其处于睡眠状态时重新获得访问权限。

                【讨论】:

                  【解决方案11】:
                         try {
                              TimeUnit.MILLISECONDS.sleep(1000L);
                          } catch (InterruptedException e) {
                              // handle
                          }
                  

                  【讨论】:

                    猜你喜欢
                    • 2012-07-26
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-01-13
                    • 2012-04-08
                    • 2023-03-12
                    相关资源
                    最近更新 更多