繁体   English   中英

Thread.Sleep() 的替代方案

[英]Alternatives to Thread.Sleep()

每 N 分钟我们要运行一个任务列表。 所以我们创建了一个任务执行器

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

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

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

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

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

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

我使用这个片段:

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

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

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

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

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

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

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

话虽如此,大多数情况下,像这样的操作更适合使用计时器。

我能想到的三个选项是:

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

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

我会使用一个等待计时器来发出 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));
    }
}

如果您的所有线程都在执行以下操作:

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

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

正如其他回答者所说,计时器可能适合您。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM