Надежно остановите System.Threading.Timer?
Хорошо, я много искал для решения этого. Я ищу чистый и простой способ предотвратить метод callback метода System.Threading.Timer после того, как я его остановил.
Кажется, я ничего не могу найти, и это привело меня к тому, чтобы прибегнуть к ужасной нитевой нить .sleep-thread.abort combo дрожь.
Можно ли это сделать с помощью блокировки? Пожалуйста, помогите мне найти хороший способ сделать это. Благодаря
Ответы
Ответ 1
like Конрад Фрикс предложил вам использовать класс System.Timers.Timer
, например:
private System.Timers.Timer _timer = new System.Timers.Timer();
private volatile bool _requestStop = false;
public constructor()
{
_timer.Interval = 100;
_timer.Elapsed += OnTimerElapsed;
_timer.AutoReset = false;
_timer.Start();
}
private void OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// do work....
if (!_requestStop)
{
_timer.Start();//restart the timer
}
}
private void Stop()
{
_requestStop = true;
_timer.Stop();
}
private void Start()
{
_requestStop = false;
_timer.Start();
}
Ответ 2
Более простым решением может быть установка Timer
никогда не возобновлять; метод Timer.Change может принимать значения для dueTime
и period
, которые указывают, что таймер никогда не перезапускается:
this.Timer.Change(Timeout.Infinite, Timeout.Infinite);
В то время как переход на использование System.Timers.Timer
может быть "лучшим" решением, всегда будут моменты, когда это непрактично; достаточно использовать Timeout.Infinite
.
Ответ 3
Для System.Threading.Timer можно сделать следующее (также будет защищать метод обратного вызова от работы с установленным таймером - ObjectDisposedException):
class TimerHelper : IDisposable
{
private System.Threading.Timer _timer;
private readonly object _threadLock = new object();
public event Action<Timer,object> TimerEvent;
public void Start(TimeSpan timerInterval, bool triggerAtStart = false,
object state = null)
{
Stop();
_timer = new System.Threading.Timer(Timer_Elapsed, state,
System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
if (triggerAtStart)
{
_timer.Change(TimeSpan.FromTicks(0), timerInterval);
}
else
{
_timer.Change(timerInterval, timerInterval);
}
}
public void Stop(TimeSpan timeout = TimeSpan.FromMinutes(2))
{
// Wait for timer queue to be emptied, before we continue
// (Timer threads should have left the callback method given)
// - http://woowaabob.blogspot.dk/2010/05/properly-disposing-systemthreadingtimer.html
// - http://blogs.msdn.com/b/danielvl/archive/2011/02/18/disposing-system-threading-timer.aspx
lock (_threadLock)
{
if (_timer != null)
{
ManualResetEvent waitHandle = new ManualResetEvent(false)
if (_timer.Dispose(waitHandle))
{
// Timer has not been disposed by someone else
if (!waitHandle.WaitOne(timeout))
throw new TimeoutException("Timeout waiting for timer to stop");
}
waitHandle.Close(); // Only close if Dispose has completed succesful
_timer = null;
}
}
}
public void Dispose()
{
Stop();
TimerEvent = null;
}
void Timer_Elapsed(object state)
{
// Ensure that we don't have multiple timers active at the same time
// - Also prevents ObjectDisposedException when using Timer-object
// inside this method
// - Maybe consider to use _timer.Change(interval, Timeout.Infinite)
// (AutoReset = false)
if (Monitor.TryEnter(_threadLock))
{
try
{
if (_timer==null)
return;
Action<Timer, object> timerEvent = TimerEvent;
if (timerEvent != null)
{
timerEvent(_timer, state);
}
}
finally
{
Monitor.Exit(_threadLock);
}
}
}
}
Вот как это можно использовать:
void StartTimer()
{
TimerHelper _timerHelper = new TimerHelper();
_timerHelper.TimerEvent += (timer,state) => Timer_Elapsed();
_timerHelper.Start(TimeSpan.FromSeconds(5));
System.Threading.Sleep(TimeSpan.FromSeconds(12));
_timerHelper.Stop();
}
void Timer_Elapsed()
{
// Do what you want to do
}
Ответ 4
Для чего это стоит, мы используем этот шаблон совсем немного:
// set up timer
Timer timer = new Timer(...);
...
// stop timer
timer.Dispose();
timer = null;
...
// timer callback
{
if (timer != null)
{
..
}
}
Ответ 5
Документы MSDN предлагают использовать метод Dispose(WaitHandle)
, чтобы остановить таймер +, чтобы быть информированным, когда обратные вызовы больше не будут вызываться.
Ответ 6
Мне кажется, это правильный путь:
Просто позвоните dispose
, когда закончите с таймером. Это остановит таймер и предотвратит будущие запланированные вызовы.
См. пример ниже.
class Program
{
static void Main(string[] args)
{
WriteOneEverySecond w = new WriteOneEverySecond();
w.ScheduleInBackground();
Console.ReadKey();
w.StopTimer();
Console.ReadKey();
}
}
class WriteOneEverySecond
{
private Timer myTimer;
public void StopTimer()
{
myTimer.Dispose();
myTimer = null;
}
public void ScheduleInBackground()
{
myTimer = new Timer(RunJob, null, 1000, 1000);
}
public void RunJob(object state)
{
Console.WriteLine("Timer Fired at: " + DateTime.Now);
}
}
Ответ 7
Этот ответ относится к System.Threading.Timer
Я прочитал много глупостей о том, как синхронизировать удаление System.Threading.Timer
по всей сети. Поэтому, почему я публикую это, чтобы немного исправить ситуацию. Не стесняйтесь сказать мне/позвонить мне, если что-то я пишу неправильно, -)
Ловушки
По-моему, эти подводные камни:
-
Timer.Dispose(WaitHandle)
может возвращать false. Он делает это, если он уже был удален (я должен был смотреть на исходный код). В этом случае он не установит WaitHandle
- так что не ждите его!
- не обрабатывает тайм-аут
WaitHandle
. Серьезно - чего вы ждете, если вас не интересует таймаут?
- Concurrency выдайте, как указано здесь, в msdn, где
ObjectDisposedException
может произойти во время (не после) удаления.
-
Timer.Dispose(WaitHandle)
работает неправильно - Slim
waithandles, или нет, как и следовало ожидать. Например, следующее не работает (оно блокируется навсегда):
using(var manualResetEventSlim = new ManualResetEventSlim)
{
timer.Dispose(manualResetEventSlim.WaitHandle);
manualResetEventSlim.Wait();
}
Решение
Ну, название немного "смелое", я думаю, но ниже моя попытка разобраться с проблемой - обертка, которая обрабатывает двойное удаление, таймауты и ObjectDisposedException
. Он не предоставляет все методы на Timer
, хотя, но не стесняйтесь их добавлять.
internal class Timer
{
private readonly TimeSpan _disposalTimeout;
private readonly System.Threading.Timer _timer;
private bool _disposeEnded;
public Timer(TimeSpan disposalTimeout)
{
_disposalTimeout = disposalTimeout;
_timer = new System.Threading.Timer(HandleTimerElapsed);
}
public event Signal Elapsed;
public void TriggerOnceIn(TimeSpan time)
{
try
{
_timer.Change(time, Timeout.InfiniteTimeSpan);
}
catch (ObjectDisposedException)
{
// race condition with Dispose can cause trigger to be called when underlying
// timer is being disposed - and a change will fail in this case.
// see
// https://msdn.microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2
if (_disposeEnded)
{
// we still want to throw the exception in case someone really tries
// to change the timer after disposal has finished
// of course there a slight race condition here where we might not
// throw even though disposal is already done.
// since the offending code would most likely already be "failing"
// unreliably i personally can live with increasing the
// "unreliable failure" time-window slightly
throw;
}
}
}
private void HandleTimerElapsed(object state)
{
Elapsed.SafeInvoke();
}
public void Dispose()
{
using (var waitHandle = new ManualResetEvent(false))
{
// returns false on second dispose
if (_timer.Dispose(waitHandle))
{
if (!waitHandle.WaitOne(_disposalTimeout))
{
throw new TimeoutException(
"Timeout waiting for timer to stop. (...)");
}
_disposeEnded = true;
}
}
}
}
Ответ 8
Возможно, вы должны сделать обратное. Используйте system.timers.timer, установите для параметра AutoReset значение false и запустите его, когда хотите
Ответ 9
Вы не можете гарантировать, что ваш код, который должен был остановить таймер, будет выполняться перед вызовом события таймера.
Например, предположим, что в момент времени 0 вы инициализировали таймер для вызова события, когда наступает момент 5. Затем в момент времени 3 вы решили, что вам больше не нужен звонок. И называется метод, который вы хотите написать здесь. Тогда, в то время как метод был JIT-ted, приходит момент времени 4, и ОС решает, что ваша нить исчерпала свой временной срез и переключатель. И таймер будет вызывать событие независимо от того, как вы пытаетесь - у вашего кода просто не будет возможности работать в худшем случае.
Вот почему безопаснее предоставлять некоторую логику в обработчике событий. Возможно, какой-то ManualResetEvent будет Reset, как только вам больше не понадобится вызов события. Итак, вы выбрали таймер, а затем установите ManualResetEvent. А в обработчике событий таймера вы сначала проверяете ManualResetEvent. Если он находится в состоянии Reset - немедленно вернитесь. Таким образом, вы можете эффективно защищать от нежелательного выполнения какого-либо кода.
Ответ 10
Вы можете остановить таймер, создав класс, подобный этому, и вызвав его, например, ваш метод обратного вызова:
public class InvalidWaitHandle : WaitHandle
{
public IntPtr Handle
{
get { return InvalidHandle; }
set { throw new InvalidOperationException(); }
}
}
Мгновенный таймер:
_t = new Timer(DisplayTimerCallback, TBlockTimerDisplay, 0, 1000);
Затем внутри метода обратного вызова:
if (_secondsElapsed > 80)
{
_t.Dispose(new InvalidWaitHandle());
}
Ответ 11
Существует ссылка MSDN, как правильно установить стоп-таймер. Используйте метод ControlThreadProc()
с событием HandleElapsed(object sender, ElapsedEventArgs e)
, синхронизированным переменной syncPoint
static class. Прокомментируйте Thread.Sleep(testRunsFor);
на ControlThreadProc()
, если он не подходит (возможно).
Ключ заключается в использовании статической переменной и атомной операции типа Interlocked.CompareExchange
для условных операторов.
Ссылка:
Timer.Stop Method