在定时器回调方法中使用 CancellationTokens
Using CancellationTokens in Timer callback method
我正在实施一个 Windows Service
,它将执行一些昂贵的 Database operations
,例如从一些表中检索数据并将其存储到其他表中,目的是创建摘要、处理报告数据等。
我正在使用 Timer object
来控制我希望操作发生的时间,这一切都很好,我这里的主要问题是当有人决定停止时如何进行取消service
或 Windows 重新启动,任何调用 OnStop()
方法的东西。
我的代码如下所示:
代码:
tmr = new Timer(new TimerCallback(tmr_Tick), cTs.Token, 0, 1000);
我在服务中有一个 CancellationTokenSource
作为名为 cTs
的全局变量。
当 OnStop
方法被调用时,我只是设置 cTs.Cancel()
我的 Callback
看起来像这样:
回调:
private void tmr_Tick(object sender)
{
if (((CancellationToken)sender).IsCancellationRequested)
{
tmr.Change(Timeout.Infinite, Timeout.Infinite);
return;
}
if (DateTime.Now.TimeOfDay.Equals(intervalRepetition))
Task.Run(async () => { await ProcessData((CancellationToken)sender); });
}
我的问题是关于 TimerCallback
,因为我没有在其中找到任何有助于处理令牌的东西,所以我将令牌作为 State object
传递,但我不确定是否这是最好的方法,所以我正在寻求建议。
还有设置了IsCancellationRequested
怎么办?我是否应该实施更好的退出策略,或许抛出取消异常?
令牌的传播向下到 ProcessData
方法是否正确?如果我从上面 return , ProcessData
方法将回答令牌?我认为它会,但我不完全确定,因为它是异步的等等。
您不仅需要请求取消当前活动的工作,而且实际上需要等待所有活动任务对取消做出反应并完成。在您当前的方法中,您这样做:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public override void OnStop() {
_cts.Cancel();
}
这不会有任何帮助,因为一旦请求取消,您将 return 从 OnStop 方法和所有后台线程(由于 Task.Run
而在其上执行您的工作)将没等他们完成就被杀了。
因此您需要一些方法来等待它们在 return 从 OnStop
开始之前完成。一种方法是使用 Monitor.Wait
和 Monitor.Pulse
,像这样:
private readonly object _activeTasksLock = new object();
private int _activeTasks;
private CancellationTokenSource _cts = new CancellationTokenSource();
private async Task ProcessData(CancellationToken ct) {
lock (_activeTasksLock) {
// increment number of active tasks
_activeTasks++;
}
try {
// do stuff
await Task.Delay(1000, ct);
// check cancellation token and exit if necessary
}
finally {
lock (_activeTasksLock) {
// decrement and notify
_activeTasks--;
Monitor.Pulse(_activeTasksLock);
}
}
}
public override void OnStop() {
// dispose\stop your timer here
// then cancel
_cts.Cancel();
// then wait until all is done
lock (_activeTasksLock) {
while (_activeTasks > 0)
Monitor.Wait(_activeTasksLock);
}
}
我正在实施一个 Windows Service
,它将执行一些昂贵的 Database operations
,例如从一些表中检索数据并将其存储到其他表中,目的是创建摘要、处理报告数据等。
我正在使用 Timer object
来控制我希望操作发生的时间,这一切都很好,我这里的主要问题是当有人决定停止时如何进行取消service
或 Windows 重新启动,任何调用 OnStop()
方法的东西。
我的代码如下所示:
代码:
tmr = new Timer(new TimerCallback(tmr_Tick), cTs.Token, 0, 1000);
我在服务中有一个 CancellationTokenSource
作为名为 cTs
的全局变量。
当 OnStop
方法被调用时,我只是设置 cTs.Cancel()
我的 Callback
看起来像这样:
回调:
private void tmr_Tick(object sender)
{
if (((CancellationToken)sender).IsCancellationRequested)
{
tmr.Change(Timeout.Infinite, Timeout.Infinite);
return;
}
if (DateTime.Now.TimeOfDay.Equals(intervalRepetition))
Task.Run(async () => { await ProcessData((CancellationToken)sender); });
}
我的问题是关于 TimerCallback
,因为我没有在其中找到任何有助于处理令牌的东西,所以我将令牌作为 State object
传递,但我不确定是否这是最好的方法,所以我正在寻求建议。
还有设置了IsCancellationRequested
怎么办?我是否应该实施更好的退出策略,或许抛出取消异常?
令牌的传播向下到 ProcessData
方法是否正确?如果我从上面 return , ProcessData
方法将回答令牌?我认为它会,但我不完全确定,因为它是异步的等等。
您不仅需要请求取消当前活动的工作,而且实际上需要等待所有活动任务对取消做出反应并完成。在您当前的方法中,您这样做:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public override void OnStop() {
_cts.Cancel();
}
这不会有任何帮助,因为一旦请求取消,您将 return 从 OnStop 方法和所有后台线程(由于 Task.Run
而在其上执行您的工作)将没等他们完成就被杀了。
因此您需要一些方法来等待它们在 return 从 OnStop
开始之前完成。一种方法是使用 Monitor.Wait
和 Monitor.Pulse
,像这样:
private readonly object _activeTasksLock = new object();
private int _activeTasks;
private CancellationTokenSource _cts = new CancellationTokenSource();
private async Task ProcessData(CancellationToken ct) {
lock (_activeTasksLock) {
// increment number of active tasks
_activeTasks++;
}
try {
// do stuff
await Task.Delay(1000, ct);
// check cancellation token and exit if necessary
}
finally {
lock (_activeTasksLock) {
// decrement and notify
_activeTasks--;
Monitor.Pulse(_activeTasksLock);
}
}
}
public override void OnStop() {
// dispose\stop your timer here
// then cancel
_cts.Cancel();
// then wait until all is done
lock (_activeTasksLock) {
while (_activeTasks > 0)
Monitor.Wait(_activeTasksLock);
}
}