【问题标题】:Windows Service Task - Polling CancellationWindows 服务任务 - 轮询取消
【发布时间】: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 还是他们做同样的事情而只需要一个?

【问题讨论】:

  • 您希望在轮询之间暂停执行吗?

标签: c# .net windows-services polling cancellation-token


【解决方案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(),我假设应该暂停执行并没有错。
猜你喜欢
  • 1970-01-01
  • 2017-01-22
  • 1970-01-01
  • 2016-10-15
  • 1970-01-01
  • 2011-08-24
  • 2015-04-08
  • 2013-11-11
  • 1970-01-01
相关资源
最近更新 更多