Windows 服务任务 - 轮询取消
Posted
技术标签:
【中文标题】Windows 服务任务 - 轮询取消【英文标题】:Windows Service Task - Polling Cancellation 【发布时间】:2019-03-21 12:17:34 【问题描述】:我正在编写一个 windows 服务,并找到了一个示例,它建议编写一个轮询 windows 服务,如下所示:
private void Poll()
CancellationToken cancellationPoll = ctsPoll.Token;
while (!cancellationPoll.WaitHandle.WaitOne(tsInterval))
PollDatabase();
// Occasionally check the cancellation state.
if (cancellationPoll.IsCancellationRequested)
break;
在取消时我有点困惑,如果我同时需要 cancelPoll.WaitHandle.WaitOne() 和 cancelPoll.IsCancellationRequested 还是他们做同样的事情而只需要一个?
【问题讨论】:
您希望在轮询之间暂停执行吗? 【参考方案1】:!cancellationPoll.WaitHandle.WaitOne(tsInterval)
用于确保轮询间隔,因此在轮询之间至少有tsIntetval
(+ 操作持续时间):
--tsInterval--|--operation--|--tsInterval--|...
如果您查看CancellationToken.WaitHandle
的文档,它会显示以下内容:
在取消令牌时发出信号的 WaitHandle。
因此,在您的情况下,操作 cancellationPoll.IsCancellationRequested
就足够了,因为在它之后您没有任何东西。但是想象一下这样的情况:
while (!cancellationPoll.WaitHandle.WaitOne(tsInterval))
//long operation A
if (cancellationPoll.IsCancellationRequested)
break;
//long operation B
if (cancellationPoll.IsCancellationRequested)
break;
//long operation C
在这种情况下,偶尔检查取消状态以避免运行长时间操作是有意义的......
【讨论】:
我仍然没有多大意义,因为您可以将第三个`IsCancellationRequested`放在处理的一致性上,并避免像本主题中出现的混淆。 关键是如果令牌已发出信号,您甚至不想运行操作 B 或操作 C,因此您不想等待下一个循环迭代...是的,您可以放也是第三个,但收益可以忽略不计...... 我只从IsCancellationRequested
与WaitHandle
的角度考虑推理,并没有看到任何混合这些方法的理由,因为混合有明显的缺点,同样的结果只能通过使用 3 IsCancellationRequested
.
@DmytroMukalov 有什么明显的缺点?
最明显的是可读性——你会发现它会让人混淆,但应该很容易阅读。如果您的代码阅读器熟悉一种方法而不熟悉另一种方法,那么他/她将深入研究每种方法的细节。对我来说,有足够的理由避免这种情况,但另外还有与资源分配相关的方面 - 如果CancellationToken
来自外部组件,您无法控制CancellationTokenSource
生命周期无法正确实施的机会,并且通过访问WaitHandle
,您可以在 CTS 中隐式分配新资源。【参考方案2】:
WaitHanlde
的等待在这里是多余的,因为从结果的角度来看,它与 IsCancellationRequested
的作用相同 - 表示请求取消(但方式略有不同)。因此,对于您的情况,您可以选择单一方法:WaitHandle
或 IsCancellationRequested
。但请记住WaitHandle
是IDisposable
并且需要处理关联的CancellationTokenSource
。如果您选择使用IsCancellationRequested
,请不要忘记添加一个应该重新调度线程的调用,例如Thread.Sleep
,以免过度使用CPU 资源。
可以应用WaitHanlde
的一种情况是,当您需要等待句柄并希望在此等待中引入取消语义时:
WaitHandle.WaitAny(new [] handleToWait, cancellationHandle );
【讨论】:
两种方法都做同样的事情是不正确的。等待句柄解决方案解决了在线程等待下一次轮询迭代时被 abel 取消的问题。IsCancellationRequested
解决方案解决了在轮询迭代进行时取消的问题。
@Ackdari 如果您仔细阅读,您会发现我从结果的角度指定它们执行相同的操作 - 您可以在源代码中检查它。他们这样做的方式是不同的 - 这正是我所说的 - 那么我的陈述有什么问题?
@DmytroMualov 说两种解决方案产生相同的行为是错误的。如果您将WaitOne()
替换为true
并添加对Thread.Sleep()
的调用,假设等待时间为5 分钟。这将导致操作被取消但5分钟不知道的情况。
@Ackdari 遗憾的是,您无法仔细阅读,因为行为和结果不是一回事。你为什么要提到Thread.Sleep
?它与取消令牌无关。我将其添加为调用示例,以从调度队列中删除线程,但不是为了等待取消令牌的功能。如果还有其他线程重新调度调用(IO、等待操作),则不需要。除非您等待另一个句柄,否则长时间等待取消的功能是多余的,因此 5 分钟的示例是无关紧要的。【参考方案3】:
需要!cancellationPoll.WaitHandle.WaitOne(tsInterval)
,这样您就不必一直等待。 WaitOne(tsInterval)
将返回,因为令牌收到要取消的信号,或者因为时间用完。如果令牌收到取消信号WaitOne(tsInterval)
将返回true
并结束循环。
例如,如果您要执行以下操作:
while(true)
// long operation
if (cancellationPoll.IsCancellationRequested)
break;
Thread.Sleep(tsInterval);
如果在线程被Thread.Sleep()
阻塞时请求取消,则整个操作直到Thread.Sleep()
完成并且下一个循环运行到达if
语句时才知道请求取消。
【讨论】:
Thread.Sleep
是那里的猜测,是否需要它是未知的。考虑到PollDatabase
意味着一些 I/O,它根本不需要。但是,即使您在一般情况下需要暂停线程调度的功能,通常也会使用非常短的周期,以数十毫秒或更短的时间测量,并且考虑到取消信号出现一次,在一般情况下,您将仅获得此期间的一半损失一次,在绝大多数已知的取消情况下可以忽略不计。
@DmytroMukalov 考虑到在原始代码中tsInterval
被赋予WaitOne()
,我假设应该暂停执行并没有错。以上是关于Windows 服务任务 - 轮询取消的主要内容,如果未能解决你的问题,请参考以下文章
背水一战 Windows 10 (109) - 通知(Tile): 按计划显示 tile 通知, 轮询服务端以更新 tile 通知