Сравните, используя Thread.Sleep и Timer для отложенного выполнения - PullRequest
55 голосов
/ 24 декабря 2008

У меня есть метод, который должен быть отложен на указанное время.

Должен ли я использовать

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

Или

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

Я читал статей об использовании Thread.Sleep - это плохой дизайн. Но я не очень понимаю, почему.

Но для использования Таймера у Таймера есть метод утилизации. Поскольку выполнение отложено, я не знаю, как распорядиться таймером. У вас есть предложения?

Или, если у вас есть альтернативные коды для отложенного выполнения, также приветствуем.

Ответы [ 6 ]

42 голосов
/ 24 декабря 2008

Одно отличие состоит в том, что System.Threading.Timer отправляет обратный вызов в потоке пула потоков, а не каждый раз создает новый поток. Если вам нужно, чтобы это происходило более одного раза в течение срока службы вашего приложения, это сэкономит накладные расходы на создание и уничтожение группы потоков (процесс, который очень ресурсоемкий, как указано в статье, на которую вы ссылаетесь), поскольку просто повторно используйте потоки в пуле, и если у вас будет более одного таймера, работающего одновременно, это означает, что у вас будет меньше потоков, работающих одновременно (также экономя значительные ресурсы).

Другими словами, Timer будет гораздо более эффективным. Это также может быть более точным, так как Thread.Sleep гарантированно будет ожидать по крайней мере столько времени, сколько вы укажете (ОС может перевести его в спящий режим гораздо дольше). Конечно, Timer все еще не будет точно точным, но цель состоит в том, чтобы вызвать обратный вызов как можно ближе к указанному времени, в то время как это НЕ обязательно намерение Thread.Sleep.

Что касается уничтожения Timer, обратный вызов может принять параметр, поэтому вы можете передать сам Timer в качестве параметра и вызвать Dispose в обратном вызове (хотя я не пробовал это - я возможно, таймер может быть заблокирован во время обратного вызова).

Редактировать: Нет, я полагаю, вы не можете этого сделать, поскольку вам нужно указать параметр обратного вызова в самом конструкторе 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() что-то делает с пользовательским интерфейсом, тогда лучше использовать System.Windows.Forms.Timer, поскольку он будет выполнять обратный вызов в потоке пользовательского интерфейса. Однако, если это не так, и он до Thread.Sleep против Threading.Timer, Threading.Timer - путь.

16 голосов
/ 31 июля 2009

используйте 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);
14 голосов
/ 24 декабря 2008

Я думаю, что Thread.Sleep - это хорошо, если вы действительно хотите приостановить приложение на указанное время. Я думаю, что причина, по которой люди говорят, что это плохой дизайн, в том, что в большинстве случаев люди на самом деле не хотят, чтобы приложение приостанавливалось.

Например, я работал на клиенте pop3, где программист использовал Thread.Sleep (1000), чтобы ждать, пока сокет получит почту. В этой ситуации было бы лучше подключить обработчик событий к сокету и продолжить выполнение программы после завершения работы сокета.

2 голосов
/ 22 июля 2010

Я помню, как реализовывал решение, аналогичное решению Эрика. Это, однако, рабочий;)

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; }
    }
1 голос
/ 24 декабря 2008

Единственный недостаток, который у меня есть с System.Timer, это то, что большую часть времени я видел, как он использовался для длительных задержек (часов, минут) в службах опроса, и разработчики часто забывают запустить событие До они запускают таймер. Это означает, что если я запускаю приложение или службу, мне нужно подождать, пока истечет таймер (часы, минуты), прежде чем он действительно запустится.

Конечно, это не проблема с таймером, но я думаю, что его часто используют ненадлежащим образом, потому что его слишком легко использовать неправильно.

0 голосов
/ 31 июля 2009

@ miniscalope Нет, не используйте ThreadPool.RegisterWaitForSingleObject вместо таймера, System.Threading.Timer поставит в очередь обратный вызов, который будет выполнен в потоке пула потоков, когда время истечет и не требует дескриптора ожидания один объект свяжет поток пула потоков, ожидая, когда будет сообщено событие, или истечет время ожидания до того, как поток вызовет обратный вызов.

...