Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Синхронизация метода Timers.Timer Elapsed при остановке

Автор вопроса: AndyОтветов: 5

Со ссылкой на эту цитату из MSDN о Система.Таймеры.Таймер:

Событие Timer.Elapsed возникает в потоке ThreadPool, поэтому метод обработки событий может выполняться в одном потоке одновременно с вызовом метода Timer.Stop в другом потоке. Это может привести к возникновению события Elapsed после вызова метода Stop. Это состояние гонки нельзя предотвратить, просто сравнивая свойство SignalTime со временем вызова метода Stop, поскольку метод обработки событий может уже выполняться в момент вызова метода Stop или может начать выполняться между моментом вызова метода Stop. вызывается и момент сохранения времени остановки. Если крайне важно предотвратить выполнение потока, вызывающего метод Stop, пока выполняется метод обработки событий, используйте более надежный механизм синхронизации, например класс Monitor или метод CompareExchange. Код, использующий метод CompareExchange, можно найти в примере для метода Timer.Stop.

Может ли кто-нибудь привести пример "надежного механизма синхронизации, такого как класс Monitor", чтобы объяснить, что именно это означает?

Я думаю, что это означает как-то использовать блокировку, но я не уверен, как вы это реализуете.

timersynchronization

Ответы

Ответ 1 · рейтинг 12 · Hans Passant

Надежная остановка System.Timers.Timer действительно является серьезной задачей. Самая серьезная проблема заключается в том, что потоки пула потоков, которые он использует для вызова события Elapsed, могут создавать резервные копии из-за алгоритма планировщика пула потоков. Наличие пары резервных вызовов не является чем-то необычным, наличие сотен технически возможно.

Вам понадобятся две синхронизации: одна, чтобы гарантировать, что вы остановите таймер только тогда, когда не запущен обработчик событий Elapsed, другая, чтобы гарантировать, что эти резервные потоки TP не причинят никакого вреда. Так:

    System.Timers.Timer timer = new System.Timers.Timer();
    object locker = new object();
    ManualResetEvent timerDead = new ManualResetEvent(false);

    private void Timer_Elapsed(object sender, ElapsedEventArgs e) {
        lock (locker) {
            if (timerDead.WaitOne(0)) return;
            // etc...
        }
    }

    private void StopTimer() {
        lock (locker) {
            timerDead.Set();
            timer.Stop();
        }
    }

Попробуйте установить для свойства AutoReset значение false. С другой стороны, это хрупко: событие Elapsed вызывается из внутреннего метода .NET, который перехватывает Exception. Очень неприятно, ваш код таймера перестает работать без какой-либо диагностики. Я не знаю истории, но, должно быть, в MSFT была другая команда, которая пыхтела и пыхтела над этим беспорядком и написала System.Threading.Timer. Настоятельно рекомендуется.

Ответ 2 · Reed Copsey

Это то, что он предлагает.

Monitor - это класс, используемый компилятором C# для заявление lock.

При этом вышеизложенное является проблемой только в том случае, если это проблема в вашей ситуации. Весь оператор в основном переводится как "Вы можете получить событие таймера, которое происходит сразу после вызова Stop(). Если это проблема, вам нужно с ней справиться". В зависимости от того, что делает ваш таймер, это может быть проблемой, а может и нет.

Если это проблема, Timer.Stop показывает надежный способ (с использованием Interlocked.CompareExchange) справиться с этим. Просто скопируйте код из примера и измените при необходимости.

Ответ 3 · bbosak

Пытаться:

lock(timer) {
timer.Stop();
}

Ответ 4 · MusiGenesis

Вот очень простой способ предотвратить возникновение этого состояния гонки:

private object _lock = new object();
private Timer _timer; // init somewhere else

public void StopTheTimer()
{
    lock (_lock)
    {
        _timer.Stop();
    }
}

void elapsed(...)
{
    lock (_lock)
    {
        if (_timer.Enabled) // prevent event after Stop() is called
        {
            // do whatever you do in the timer event
        }
    }
}

Ответ 5 · Bengie

Кажется, таймер не является потокобезопасным. Вы должны синхронизировать все вызовы с ним с помощью блокировки. lock(object){} на самом деле просто сокращение для простого вызова монитора.

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы