Как изящно остановить System.Threading.Timer?
У меня есть служба Windows, реализованная на С#, которая должна выполнять некоторую работу так часто. Я реализовал это с помощью System.Threading.Timer
с методом обратного вызова, который отвечает за планирование следующего обратного вызова. У меня возникли проблемы с изящной остановкой (т.е. Утилизации) таймера. Вот несколько упрощенных кодов, которые вы можете запустить в консольном приложении, которое иллюстрирует мою проблему:
const int tickInterval = 1000; // one second
timer = new Timer( state => {
// simulate some work that takes ten seconds
Thread.Sleep( tickInterval * 10 );
// when the work is done, schedule the next callback in one second
timer.Change( tickInterval, Timeout.Infinite );
},
null,
tickInterval, // first callback in one second
Timeout.Infinite );
// simulate the Windows Service happily running for a while before the user tells it to stop
Thread.Sleep( tickInterval * 3 );
// try to gracefully dispose the timer while a callback is in progress
var waitHandle = new ManualResetEvent( false );
timer.Dispose( waitHandle );
waitHandle.WaitOne();
Проблема заключается в том, что я получаю ObjectDisposedException
из timer.Change
в потоке обратного вызова, а waitHandle.WaitOne
блокируется. Что я делаю неправильно?
Документация для перегрузки Dispose
, которую я использую, говорит:
Таймер не устанавливается до тех пор, пока все завершенные в настоящий момент обратные вызовы не будут завершены.
Изменить: похоже, что это утверждение из документации может быть неправильным. Может кто-нибудь проверить?
Я знаю, что я мог бы решить проблему, добавив некоторую сигнализацию между обратным вызовом и кодом удаления, как предложил Хенк Холтерман ниже, но я не хочу этого делать, если это абсолютно необходимо.
Ответы
Ответ 1
С помощью этого кода
timer = new Timer( state => {
// simulate some work that takes ten seconds
Thread.Sleep( tickInterval * 10 );
// when the work is done, schedule the next callback in one second
timer.Change( tickInterval, Timeout.Infinite );
},
null,
tickInterval, // first callback in one second
Timeout.Infinite );
почти наверняка вы будете утилизировать таймер во время сна.
Вам нужно будет защитить код после Sleep(), чтобы обнаружить установленный таймер. Поскольку свойство IsDisposed отсутствует, быстрый и грязный static bool stopping = false;
может сделать трюк.
Ответ 2
Возможное решение для защиты метода обратного вызова от работы с установленным таймером:
fooobar.com/questions/135536/...
Ответ 3
Как описано в "Параллельное программирование в Windows":
Создайте фиктивный класс InvalidWaitHandle, наследующий от WaitHandle:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Threading;
namespace MyNameSpace
{
class InvalidWaitHandle : WaitHandle
{
}
}
Следовательно, вы можете правильно утилизировать System.Threading.Timer следующим образом:
public static void DisposeTimer()
{
MyTimer.Dispose(new InvalidWaitHandle());
MyTimer = null;
}
Ответ 4
Вам не нужно избавляться от таймера, чтобы остановить его. Вы можете вызвать Timer.Stop()
или установить Timer.Enabled
на false
, либо из которого остановится таймер.