Не реентерабельные таймеры - PullRequest
6 голосов
/ 14 августа 2011

У меня есть функция, которую я хочу вызывать каждые x секунд, но я хочу, чтобы она была поточно-ориентированной.

Можно ли настроить это поведение при создании таймера? (Я не против того, какой таймер .NET я использую, я просто хочу, чтобы он был потокобезопасным).

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

Моя функция обратного вызова и окружение не связаны с пользовательским интерфейсом.

[Изменить 1] Я просто не хочу, чтобы внутри моей функции обратного вызова было больше одного потока.

[Изменить 2] Я хочу сохранить блокировку на уровне таймера, потому что таймер отвечает за то, когда вызывать мой обратный вызов, и здесь есть особая ситуация, когда я не хочу вызывать свою функцию обратного вызова. Поэтому я думаю , когда звонить - это ответственность таймера .

Ответы [ 5 ]

17 голосов
/ 14 августа 2011

Полагаю, поскольку ваш вопрос не совсем понятен, вы хотите убедиться, что ваш таймер не может повторно ввести ваш обратный вызов, пока вы обрабатываете обратный вызов, и вы хотите сделать это без блокировки.Вы можете достичь этого, используя System.Timers.Timer и убедившись, что для свойства AutoReset установлено значение false.Это гарантирует, что вам придется запускать таймер на каждом интервале вручную, таким образом предотвращая любой повторный вход:

public class NoLockTimer : IDisposable
{
    private readonly Timer _timer;

    public NoLockTimer()
    {
        _timer = new Timer { AutoReset = false, Interval = 1000 };

        _timer.Elapsed += delegate
        {
            //Do some stuff

            _timer.Start(); // <- Manual restart.
        };

        _timer.Start();
    }

    public void Dispose()
    {
        if (_timer != null)
        {
            _timer.Dispose();
        }
    }
} 
6 голосов
/ 25 марта 2013

В дополнение к решению Тима Ллойда для System.Timers.Timer, вот решение для предотвращения повторного входа в тех случаях, когда вы хотите использовать System.Threading.Timer вместо.

TimeSpan DISABLED_TIME_SPAN = TimeSpan.FromMilliseconds(-1);

TimeSpan interval = TimeSpan.FromSeconds(1);
Timer timer = null; // assign null so we can access it inside the lambda

timer = new Timer(callback: state =>
{
  doSomeWork();
  try
  {
    timer.Change(interval, DISABLED_TIME_SPAN);
  }
  catch (ObjectDisposedException timerHasBeenDisposed)
  {
  }
}, state: null, dueTime: interval, period: DISABLED_TIME_SPAN);

Я полагаю, что вы не хотите, чтобы interval был доступен внутри обратного вызова, но это легко исправить, если вы хотите: Поместите вышеприведенное в класс NonReentrantTimer, который охватывает BCL Timer учебный класс. Затем вы должны передать обратный вызов doSomeWork в качестве параметра. Пример такого класса:

public class NonReentrantTimer : IDisposable
{
    private readonly TimerCallback _callback;
    private readonly TimeSpan _period;
    private readonly Timer _timer;

    public NonReentrantTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
    {
        _callback = callback;
        _period = period;
        _timer = new Timer(Callback, state, dueTime, DISABLED_TIME_SPAN);
    }

    private void Callback(object state)
    {
        _callback(state);
        try
        {
            _timer.Change(_period, DISABLED_TIME_SPAN);
        }
        catch (ObjectDisposedException timerHasBeenDisposed)
        {
        }
    }


    public void Dispose()
    {
        _timer.Dispose();
    }
}
2 голосов
/ 14 августа 2011

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

Если блокировка необходима, то как можнотаймер это устроит?Вы ищете волшебную халяву.

Re Edit1:

На ваш выбор System.Timers.Timer и System.Threading.Timer, оба требуют предосторожности против повторного входа.См. эту страницу и найдите раздел Работа с таймером повторного входа .

0 голосов
/ 10 июня 2015
/// <summary>
/// The C#6 version
/// </summary>
public class TimerNicer : IDisposable {
    private Action OnElapsed { get; }

    [NotNull]
    private System.Timers.Timer Timer { get; } = new System.Timers.Timer { AutoReset = false, Interval = 1 };

    public TimerNicer(Action onElapsed) {

        this.OnElapsed = onElapsed ?? ( () => {
        } );

        this.Timer.Elapsed += (sender, args) => {

            this.Timer.Stop();    // Why not stop the timer here with this?

            try {
                this.OnElapsed(); // do stuff here
            }
            catch ( Exception exception ) {
                Console.WriteLine( exception );
            }
            finally {
                this.Timer.Start();
            }
        };

        this.Timer.Start();
    }

    public void Dispose() => this.Timer.Dispose();
}
0 голосов
/ 14 августа 2011

Как таймер мог узнать о ваших общих данных?

Обратный вызов таймера выполняется в некотором потоке ThreadPool. Так что у вас будет как минимум 2 темы:

  1. Ваш главный поток, где таймер создан и запущен;
  2. Поток из ThreadPool для запуска обратного вызова.

И вы несете ответственность за правильную работу с вашими общими данными.

Ред.: Чибитт предоставил прекрасный пример.

...