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

Как изящно остановить 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, которую я использую, говорит:

Таймер не устанавливается до тех пор, пока все завершенные в настоящий момент обратные вызовы не будут завершены.

Изменить: похоже, что это утверждение из документации может быть неправильным. Может кто-нибудь проверить?

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

4b9b3361

Ответ 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, либо из которого остановится таймер.