63

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

我应该使用

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

或者

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

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

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

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

4

6 回答 6

46

一个区别是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 做了一些事情,那么使用 aSystem.Windows.Forms.Timer可能是更好的选择,因为它将在 UI 线程上运行回调。但是,如果不是这种情况,并且归结为Thread.Sleepvs. Threading.TimerThreading.Timer那就是要走的路。

于 2008-12-24T16:09:43.840 回答
18

使用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);
于 2009-07-30T23:51:11.090 回答
17

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

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

于 2008-12-24T15:16:33.370 回答
3

我记得实施了一个类似于 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; }
    }
于 2010-07-22T06:41:47.530 回答
2

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

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

于 2008-12-24T17:09:59.443 回答
1

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

于 2009-07-31T01:30:19.330 回答