Подтвердить что ты не робот

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

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

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

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. Поскольку выполнение отложено, я не знаю, как распорядиться таймером. У вас есть какие-нибудь предложения?

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

4b9b3361

Ответ 1

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

Ответ 2

используйте 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);

Ответ 3

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

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

Ответ 4

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

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; }
    }

Ответ 5

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

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

Ответ 6

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