Thread.Sleep()的替代品

时间:2011-03-24 19:48:00

标签: c# multithreading

我们每隔N分钟就要完成一系列任务。所以我们用

创建了一个任务执行器
do { DoWork(); }while(!stopRequested)

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

那么有替代方法还是我找到了Thread.Sleep的有效用途?

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

  

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

11 个答案:

答案 0 :(得分:62)

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

我使用此代码段:

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

答案 1 :(得分:23)

当然,您必须在<{1}}上致电WaitOne WaitHandle。这是一个实例方法。否则怎么知道该等什么呢?

最好让某些东西你可以做出反应而不是睡觉,这样你就可以注意到取消而无需等待分钟。 WaitHandle的另一种替代方法是使用Monitor.Wait / Pulse

但是,如果您正在使用.NET 4,我会查看任务并行库提供的内容......它的级别略高于其他选项,并且通常是经过深思熟虑的库。< / p>

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

答案 2 :(得分:7)

Thread.Sleep不是魔鬼 - 你可以将它用于这样的场景。它在短时间内不太可靠。

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

话虽如此,大多数时候,这样的操作更适合使用Timer。您是否有理由在循环中处理此问题,而不是仅仅使用Timer启动工作项?

答案 3 :(得分:5)

我能想到的三个选项是:

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

答案 4 :(得分:3)

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

答案 5 :(得分: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));
    }
}

答案 6 :(得分: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();

答案 7 :(得分:0)

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

答案 8 :(得分: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.
        }
    }
}

答案 9 :(得分:0)

我在WinForms应用程序中使用了Timer,并且在服务器上使用了ScheduledTask定期启动的控制台应用程序。带有计时器的WinForms已被用于我希望它弹出它运行的通知和它找到的内容的情况(我将它们设置为模仿系统托盘)。对于我只想在服务器上出现问题时得到通知的情况,我将它们作为控制台应用程序放在服务器上并将它们作为计划任务运行。这看起来效果很好。

我想在我现在使用Timers的应用程序中尝试使用Sleep,并且不喜欢结果。首先,使用计时器,我可以非常轻松地调用最小化的应用程序,以便在前面设置或更改设置。如果应用程序处于睡眠状态,则在睡眠时难以重新获得访问权限。

答案 10 :(得分:0)

       try {
            TimeUnit.MILLISECONDS.sleep(1000L);
        } catch (InterruptedException e) {
            // handle
        }