2011-03-24 198 views
30

每隔N分钟,我们要运行一系列任务。因此,我们已经创建了一个任务执行与Thread.Sleep的替代方案()

do { DoWork(); }while(!stopRequested) 

现在,我们希望有工作周期之间的停顿。每个人似乎都认为Thread.Sleep()是魔鬼。我已经看到使用Monitor/Event的提及,但我们没有其他人告诉我们去做工作。我们只是想每隔N分钟就像发条一样做东西。

那么有没有其他的选择或我找到了Thread.Sleep的有效使用?

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

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

回答

22

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

最好是有你可以反应而不是睡觉,这样你就可以注意到无需等待几分钟就取消了。 WaitHandle的另一种替代方法是使用Monitor.Wait/Pulse。但是,如果你使用的是.NET 4,我会研究Task Parallel Library提供的功能......它的级别比其他选项略高一些,通常是一个经过深思熟虑的库。

对于你可能想看看使用Timer(无论是System.Threading.TimerSystem.Timers.Timer),或者甚至可能Quartz.NET经常性的工作任务。

+0

你认为答案仍然有效吗? – Cer 2017-03-30 11:40:37

+0

@Cer:它应该仍然有效,当然。另一种方法是使用具有取消标记的任务。 – 2017-03-30 12:36:20

7

Thread.Sleep不是魔鬼 - 你可以用它来做这样的场景。这对于短时间来说不是很可靠。

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

这就是说,大多数情况下,像这样的操作更适合使用定时器。是否有理由试图在循环中处理这个问题,而不是仅仅使用Timer来启动工作项目?

+1

呃,我们会去缺乏想象力,因为我没有使用定时器:) – 2011-03-24 20:00:45

+0

不,'Thread.Sleep'不是魔鬼。它更像Phil的光明之王(http://en.wikipedia.org/wiki/List_of_Dilbert_characters#Phil_the_Prince_of_Insufficient_Light)。但严重的是,'Thread.Sleep'的使用相对较少。对我来说,这是代码中的红旗。大多数情况下,用计时器替换“睡眠”会产生更清晰,更健壮的代码。 – 2011-03-24 21:05:05

5

,我能想到的把我的头顶部的三个选项:

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

2

您可以使用您设置时,它的时候停止,然后一个ManualResetEvent的做就可以了WaitOne的。

2

我会用一个等待定时器;这表示的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)); 
    } 
} 
0

你可以使用一个System.Timers.Timer,并在其过去处理程序执行工作。

0

至于其他的应答者说,计时器可以为你工作好。

如果你想自己的线程,我就不会在这里使用了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. 
     } 
    } 
} 
0

我用两个定时器在WinForms应用程序和控制台应用程序启动定期由ScheduledTask的服务器上。用计时器的WinForms已经在案件中使用时,我希望它弹出的通知,它跑过去,它找到了什么(我已经设置这些来mimize到系统托盘)。对于我只想在服务器出现问题时收到通知的情况,我已将它们作为控制台应用程序放在服务器上,并将它们作为计划任务运行。这似乎工作得很好。

我想我试图在我现在用的定时器的应用程序使用睡眠和不喜欢的结果。首先,使用计时器,我可以非常轻松地调用最小化的应用程序,以设置或更改前面的设置。如果应用程序已经睡着了,那么您很难在睡眠中重新获得访问权限。

1

如果所有的线程正在做的是一样的东西:

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(); 
47

按照我的理解,Thread.sleep()方法是不好的,因为它迫使线程的资源超出缓存,所以必须在之后再次加载。没什么大不了的,但它可能会在高负载情况下加剧性能问题。然后还有一个事实,即计时不准确,它有效地不能等待的持续时间在10ms左右...

我用这个片段:

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

容易馅饼,它全部适合一条线。创建一个永远不会设置的新事件处理程序,然后等待完整的超时期限,您将其指定为参数WaitOne()

虽然,对于这种特定情况下,计时器可能会是一个更合适的方法:

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 
+2

我发现你的1行代码对于寻找thread.sleep的替代品时非常有用,我只想暂停一秒或2秒,并且每次都不能使用thread.sleep登录到控制台。 – Sam 2013-03-18 21:26:07

+2

我注意到'ManualResetEvent'是'IDisposable',但是你的代码让匿名实例变成GC'd。在这种情况下是否存在没有明确调用Dispose的风险? – Dai 2016-06-27 22:32:11

+1

好问题。根据我在反汇编中可以看到的,'ManualResetEvent'使用基本的'WaitHandle'功能,其Dispose方法仅仅处理其内部的'SafeWaitHandle'实例。 SafeWaitHandle的基类SafeHandle在其解构器中显式调用它自己的Dispose方法,所以我假设任何非托管OS句柄在该对象被GC'd处理时都会被清除。但2016年最好的选择就是使用'Task.Delay'并避免整个问题。 :) – 2016-11-21 20:01:27

-1

我发现了一种方法来破解Thread.Sleep,让程序定期唤醒,并在需要取消时执行事件。如下所示:

// Pay attention every 1/10 sec to maintain some UI responsiveness 
while (val > 0) { 
    Thread.Sleep(100); 
    val = val - 100; 
    Application.DoEvents(); 
    if (val == x) { 
     // You could do some conditional stuff here at specific times 
    } 
} 

替换val用你想要的毫秒延迟。越是降低ms值的Thread.Sleep,程序看起来就越有反应。 100ms似乎可以接受我。 50ms是最好的。