使用计时器处理 class 的正确方法是什么?
What is the proper way to Dispose of a class with a Timer?
假设我有一个 class,它有一个 Timer 对象,它不做任何关键工作——只是一些 GUI 工作。假设有 2 种情况,计时器每 5 分钟计时一次:
- 在
Timer_Elapsed
代表中有很多工作已经完成,需要 2 分钟才能完成。
- 在
Timer_Elapsed
委托中几乎没有工作要做,需要几毫秒才能完成
处理对象和计时器的正确方法是什么? Timer_Elapsed
事件委托运行的时间是否会影响您对如何正确处置的决定?
对此有多种方法,正如 Alex 在评论中所说,这取决于委托将使用的对象是否也被释放。
假设我们有一个 "worst-case" 场景,其中代理确实需要使用将被释放的对象。
处理此问题的一个好方法类似于 Process
对象具有的方法:WaitForExit()
。此方法将简单地循环,直到它看到委托已完成工作(在委托运行之前和之后设置了 working
布尔值?)然后 returns。现在你可以在代码中使用 class:
// Time to shut down
myDisposable.WaitForFinish();
myDisposable.Dispose();
因此,我们实质上是确保在处理委托之前完成委托,停止任何类型的 ObjectDisposedException
。
如果,您需要在处置期间停止您的计时器,并且依赖于共享资源的计时器委托中的工作仍在进行中,同时被处置,您需要协调 "shutdown" 过程。下面的代码片段显示了一个这样做的例子:
public class PeriodicTimerTask : IDisposable
{
private readonly System.Timers.Timer _timer;
private CancellationTokenSource _tokenSource;
private readonly ManualResetEventSlim _callbackComplete;
private readonly Action<CancellationToken> _userTask;
public PeriodicTimerTask(TimeSpan interval, Action<CancellationToken> userTask)
{
_tokenSource = new CancellationTokenSource();
_userTask = userTask;
_callbackComplete = new ManualResetEventSlim(true);
_timer = new System.Timers.Timer(interval.TotalMilliseconds);
}
public void Start()
{
if (_tokenSource != null)
{
_timer.Elapsed += (sender, e) => Tick();
_timer.AutoReset = true;
_timer.Start();
}
}
public void Stop()
{
var tokenSource = Interlocked.Exchange(ref _tokenSource, null);
if (tokenSource != null)
{
_timer.Stop();
tokenSource.Cancel();
_callbackComplete.Wait();
_timer.Dispose();
_callbackComplete.Dispose();
tokenSource.Dispose();
}
}
public void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
private void Tick()
{
var tokenSource = _tokenSource;
if (tokenSource != null && !tokenSource.IsCancellationRequested)
{
try
{
_callbackComplete.Wait(tokenSource.Token); // prevent multiple ticks.
_callbackComplete.Reset();
try
{
tokenSource = _tokenSource;
if (tokenSource != null && !tokenSource.IsCancellationRequested)
_userTask(tokenSource.Token);
}
finally
{
_callbackComplete.Set();
}
}
catch (OperationCanceledException) { }
}
}
}
用法示例:
public static void Main(params string[] args)
{
var periodic = new PeriodicTimerTask(TimeSpan.FromSeconds(1), cancel => {
int n = 0;
Console.Write("Tick ...");
while (!cancel.IsCancellationRequested && n < 100000)
{
n++;
}
Console.WriteLine(" completed.");
});
periodic.Start();
Console.WriteLine("Press <ENTER> to stop");
Console.ReadLine();
Console.WriteLine("Stopping");
periodic.Dispose();
Console.WriteLine("Stopped");
}
输出如下:
Press <ENTER> to stop
Tick ... completed.
Tick ... completed.
Tick ... completed.
Tick ... completed.
Tick ... completed.
Stopping
Stopped
假设我有一个 class,它有一个 Timer 对象,它不做任何关键工作——只是一些 GUI 工作。假设有 2 种情况,计时器每 5 分钟计时一次:
- 在
Timer_Elapsed
代表中有很多工作已经完成,需要 2 分钟才能完成。 - 在
Timer_Elapsed
委托中几乎没有工作要做,需要几毫秒才能完成
处理对象和计时器的正确方法是什么? Timer_Elapsed
事件委托运行的时间是否会影响您对如何正确处置的决定?
对此有多种方法,正如 Alex 在评论中所说,这取决于委托将使用的对象是否也被释放。
假设我们有一个 "worst-case" 场景,其中代理确实需要使用将被释放的对象。
处理此问题的一个好方法类似于 Process
对象具有的方法:WaitForExit()
。此方法将简单地循环,直到它看到委托已完成工作(在委托运行之前和之后设置了 working
布尔值?)然后 returns。现在你可以在代码中使用 class:
// Time to shut down
myDisposable.WaitForFinish();
myDisposable.Dispose();
因此,我们实质上是确保在处理委托之前完成委托,停止任何类型的 ObjectDisposedException
。
如果,您需要在处置期间停止您的计时器,并且依赖于共享资源的计时器委托中的工作仍在进行中,同时被处置,您需要协调 "shutdown" 过程。下面的代码片段显示了一个这样做的例子:
public class PeriodicTimerTask : IDisposable
{
private readonly System.Timers.Timer _timer;
private CancellationTokenSource _tokenSource;
private readonly ManualResetEventSlim _callbackComplete;
private readonly Action<CancellationToken> _userTask;
public PeriodicTimerTask(TimeSpan interval, Action<CancellationToken> userTask)
{
_tokenSource = new CancellationTokenSource();
_userTask = userTask;
_callbackComplete = new ManualResetEventSlim(true);
_timer = new System.Timers.Timer(interval.TotalMilliseconds);
}
public void Start()
{
if (_tokenSource != null)
{
_timer.Elapsed += (sender, e) => Tick();
_timer.AutoReset = true;
_timer.Start();
}
}
public void Stop()
{
var tokenSource = Interlocked.Exchange(ref _tokenSource, null);
if (tokenSource != null)
{
_timer.Stop();
tokenSource.Cancel();
_callbackComplete.Wait();
_timer.Dispose();
_callbackComplete.Dispose();
tokenSource.Dispose();
}
}
public void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
private void Tick()
{
var tokenSource = _tokenSource;
if (tokenSource != null && !tokenSource.IsCancellationRequested)
{
try
{
_callbackComplete.Wait(tokenSource.Token); // prevent multiple ticks.
_callbackComplete.Reset();
try
{
tokenSource = _tokenSource;
if (tokenSource != null && !tokenSource.IsCancellationRequested)
_userTask(tokenSource.Token);
}
finally
{
_callbackComplete.Set();
}
}
catch (OperationCanceledException) { }
}
}
}
用法示例:
public static void Main(params string[] args)
{
var periodic = new PeriodicTimerTask(TimeSpan.FromSeconds(1), cancel => {
int n = 0;
Console.Write("Tick ...");
while (!cancel.IsCancellationRequested && n < 100000)
{
n++;
}
Console.WriteLine(" completed.");
});
periodic.Start();
Console.WriteLine("Press <ENTER> to stop");
Console.ReadLine();
Console.WriteLine("Stopping");
periodic.Dispose();
Console.WriteLine("Stopped");
}
输出如下:
Press <ENTER> to stop
Tick ... completed.
Tick ... completed.
Tick ... completed.
Tick ... completed.
Tick ... completed.
Stopping
Stopped