Нерентабельные таймеры
У меня есть функция, которую я хочу вызывать каждые х секунд, но я хочу, чтобы она была потокобезопасной.
Могу ли я настроить это поведение, когда создаю таймер? (Я не против, какой .NET-таймер я использую, я просто хочу, чтобы он был потокобезопасным).
Я знаю, что могу реализовать блокировки внутри моей функции обратного вызова, но я думаю, что было бы более элегантно, если бы оно находилось на уровне таймера.
Моя функция обратного вызова и среда не связаны с пользовательским интерфейсом.
[Изменить 1]
Я просто не хочу, чтобы в моей функции обратного вызова было более одного потока.
[Редактировать 2]
Я хочу сохранить блокировку внутри уровня таймера, потому что таймер отвечает за вызов моего обратного вызова, и здесь возникает особая ситуация, когда я не хочу вызывать свою функцию обратного вызова. Поэтому я думаю, что , когда звонить - это таймер.
Ответы
Ответ 1
Я предполагаю, что, поскольку ваш вопрос не совсем ясен, вы хотите, чтобы ваш таймер не мог повторно ввести ваш обратный вызов, пока вы обрабатываете обратный вызов, и вы хотите сделать это без блокировки. Вы можете достичь этого с помощью 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();
}
}
}
Ответ 2
Дополняя решение Tim Lloyd для 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();
}
}
Ответ 3
Я знаю, что могу реализовать блокировки внутри моей функции обратного вызова, но я думаю, что это будет более элегантно, если оно будет на уровне таймера
Если требуется блокировка, как это может сделать таймер? Вы ищете волшебную халяву.
Re Edit1:
Ваш выбор - System.Timers.Timer и System.Threading.Timer, оба требуют предосторожности против повторного входа. См. эту страницу и найдите раздел Работа с реестром событий.
Ответ 4
using System;
using System.Diagnostics;
using System.Threading;
using Timer = System.Windows.Forms.Timer;
/// <summary>
/// Updated the code.
/// </summary>
public class NicerFormTimer : IDisposable {
public void Dispose() {
using ( this.Timer ) { }
}
private Boolean access { get; set; }
private Timer Timer { get; }
/// <summary>
/// Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
/// </summary>
/// <param name="action"></param>
/// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
/// <param name="milliseconds"></param>
public NicerFormTimer( Action action, Boolean repeat, Int32? milliseconds = null ) {
if ( action == null ) {
return;
}
this.Timer = new Timer {
Interval = milliseconds.GetValueOrDefault( 1 )
};
this.Timer.Tick += ( sender, args ) => {
try {
if ( Monitor.TryEnter( this.access ) ) {
this.access = true;
this.Timer.Stop();
action.Invoke();
}
}
catch ( Exception exception ) {
Debug.WriteLine( exception );
}
finally {
if ( this.access ) {
Monitor.Exit( this.access );
this.access = false;
}
if ( repeat ) {
this.Timer.Start();
}
}
};
this.Timer.Start();
}
}
/// <summary>
/// Updated the code.
/// </summary>
public class NicerSystemTimer : IDisposable {
public void Dispose() {
using ( this.Timer ) { }
}
private Boolean access { get; set; }
private System.Timers.Timer Timer { get; }
/// <summary>
/// Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
/// </summary>
/// <param name="action"></param>
/// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
/// <param name="milliseconds"></param>
public NicerSystemTimer( Action action, Boolean repeat, Double? milliseconds = null ) {
if ( action == null ) {
return;
}
this.access = false;
this.Timer = new System.Timers.Timer {
AutoReset = false, Interval = milliseconds.GetValueOrDefault( 1 )
};
this.Timer.Elapsed += ( sender, args ) => {
try {
if ( Monitor.TryEnter( this.access ) ) {
this.access = true;
this.Timer.Stop();
action.Invoke();
}
}
catch ( Exception exception ) {
Debug.WriteLine( exception );
}
finally {
if ( this.access ) {
Monitor.Exit( this.access );
this.access = false;
}
if ( repeat ) {
this.Timer.Start();
}
}
};
this.Timer.Start();
}
}
Ответ 5
Как таймер мог узнать о ваших общих данных?
Обратный вызов таймера выполняется в потоке ThreadPool. Итак, у вас будет как минимум 2 потока:
- Ваш основной поток, где таймер создан и запущен;
- Поток из ThreadPool для запуска обратного вызова.
И вы несете ответственность за правильную работу с вашими общими данными.
Re editits: chibacity - прекрасный пример.