Сравните использование Thread.Sleep и Timer для отложенного выполнения

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

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

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

Or

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

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

Но для использования Timer у Timer есть метод dispose. Поскольку выполнение задерживается, я не знаю, как избавиться от Timer. У Вас есть какие-то предложения?

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


person Chaowlert Chaisrichalermpol    schedule 24.12.2008    source источник


Ответы (6)


Одно отличие состоит в том, что 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 - это путь.

person Eric Rosenberger    schedule 24.12.2008
comment
Также стоит указать на разницу с System.Windows.Forms.Timer, который, как я полагаю, вызывает функцию в потоке пользовательского интерфейса, что действительно важно для приложений WinForms! - person Dave Markle; 24.12.2008
comment
Для будущих читателей: Sleep не является гарантированным событием, ПО КРАЙНЕЙ МЕРЕ, документально подтверждено, что оно может быть меньше. - person Peter Ritchie; 01.04.2013
comment
Из любопытства... где это задокументировано? - person Eric Rosenberger; 01.04.2013
comment
Я попробовал это и через 5 минут получил сообщение о нехватке памяти. - person software is fun; 12.03.2015
comment
@EricRosenberger - не знаю, интересно ли вам это, но я наткнулся на эту ветку и нашел ваш комментарий. Я ответил в трех частях, так как это слишком долго, чтобы уместиться в одном комментарии. - person Wai Ha Lee; 13.04.2015
comment
В документации MSDN для Thread.Sleep(Int32) говорится: фактический тайм-аут может не совпадать точно с указанным тайм-аутом, потому что указанный тайм-аут будет скорректирован так, чтобы он совпадал с тактами часов. Дополнительные сведения о разрешении часов и времени ожидания см. в функции сна< /а> тема. Этот метод вызывает функцию Sleep из системных API Windows. . - person Wai Ha Lee; 13.04.2015
comment
В документации Sleep Function говорится: Эта функция заставляет поток отказываться от остатка своего кванта времени и становится неработоспособным на интервал, зависящий от значения dwMilliseconds. Системные часы идут с постоянной скоростью. Если dwMilliseconds меньше разрешения системных часов, поток может находиться в спящем режиме меньше указанного промежутка времени. Если dwMilliseconds больше одного тика, но меньше двух, ожидание может составлять от одного до двух тиков и т. д. - person Wai Ha Lee; 13.04.2015
comment
Одна из самых неприятных вещей, которые я видел в C#, это то, что вы МОЖЕТЕ делать то, что, по вашим словам, вы не можете сделать. Конструктор таймера может получить действие, которое закрывается по самому таймеру, путем ссылки на локальную переменную, которой назначается конструктор. - person Paul K; 29.01.2020

используйте 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);
person Community    schedule 30.07.2009
comment
Прочитайте принятый ответ. Таймер уже использует пул потоков. - person John Saunders; 31.07.2009
comment
Я предпочитаю RegisterWaitForSingleObject из-за логики... Метод выполняется ОДИН РАЗ, когда время истекло... поэтому вам не нужно обманывать, чтобы остановить таймер после того, как работа выполнена нехорошо... так что RegisterWaitForSingleObject, естественно, делает именно то, что он хочет, таймер не таймер лучше, когда вы хотите выполнить задачу несколько раз через определенные промежутки времени.... - person ; 01.08.2009
comment
Я добавил пример. Более подробная информация доступна в этом вопросе / - person Greg Bray; 26.05.2012
comment
Также msdn.microsoft.com/en- мы/библиотека/ - person Greg Bray; 26.05.2012
comment
Я согласен, и это метод, который я предпочитаю. Следующий пост, который я написал, поддерживает этот ответ и подробно описывает некоторые варианты использования для тех, кто интересуется, как использовать RegisterWaitForSingleObject: allen-conway-dotnet.blogspot.com/2009/12/ - person atconway; 21.11.2012

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

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

person Shawn    schedule 24.12.2008
comment
За исключением того, что ни в одном из примеров в посте приложение не останавливается. Плакат делает паузу в отдельном потоке, не вызывая Thread.Sleep напрямую. Вызов Thread.Sleep напрямую, да, плохая идея. - person Eric Rosenberger; 24.12.2008

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

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; }
    }
person Froyke    schedule 22.07.2010

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

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

person StingyJack    schedule 24.12.2008

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

person Matt    schedule 31.07.2009
comment
У вас есть документация по этому поводу? По сути, вы говорите, что ThreadPool.RegisterWaitForSingleObject работает как Thread.Sleep, утверждая: wait for single object свяжет поток пула потоков, ожидающий сигнала о событии или истечения времени ожидания до того, как поток вызовет обратный вызов? - person atconway; 21.11.2012
comment
Он не использует Thread.Sleep... после копания в источнике подразумевается, что он вызывает собственный RegisterWaitForSingleObject (я говорю подразумевает, потому что метод, который он вызывает, является внешним, но оформлен как внутренний вызов времени выполнения...) Если это предположение верно верно, вы можете предположить (из docs), что он будет использовать поток ожидания из собственного пула потоков для ожидания сигнала объекта, а затем выполняет обратный вызов в рабочем потоке. - person Matt; 22.11.2012