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,因此您不想等待下一个循环迭代...是的,您可以放也是第三个,但收益可以忽略不计...... 我只从IsCancellationRequestedWaitHandle 的角度考虑推理,并没有看到任何混合这些方法的理由,因为混合有明显的缺点,同样的结果只能通过使用 3 IsCancellationRequested. @DmytroMukalov 有什么明显的缺点? 最明显的是可读性——你会发现它会让人混淆,但应该很容易阅读。如果您的代码阅读器熟悉一种方法而不熟悉另一种方法,那么他/她将深入研究每种方法的细节。对我来说,有足够的理由避免这种情况,但另外还有与资源分配相关的方面 - 如果CancellationToken 来自外部组件,您无法控制CancellationTokenSource 生命周期无法正确实施的机会,并且通过访问WaitHandle,您可以在 CTS 中隐式分配新资源。【参考方案2】:

WaitHanlde 的等待在这里是多余的,因为从结果的角度来看,它与 IsCancellationRequested 的作用相同 - 表示请求取消(但方式略有不同)。因此,对于您的情况,您可以选择单一方法:WaitHandleIsCancellationRequested。但请记住WaitHandleIDisposable 并且需要处理关联的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 服务任务 - 轮询取消的主要内容,如果未能解决你的问题,请参考以下文章

EF6、Windows 服务和数据库轮询

带有通知的 Windows Phone 8 后台任务

从 Windows 打印服务器轮询打印机信息

创建一个 c# windows 服务来轮询数据库

背水一战 Windows 10 (109) - 通知(Tile): 按计划显示 tile 通知, 轮询服务端以更新 tile 通知

消息过滤器 (0x80010002) 取消了呼叫,Windows Server 2008 中的任务计划程序