任务应如何正确发出 Windows 服务关闭信号?

How should a Task signal a Windows service shutdown correctly?

我正在处理 Windows 服务的错​​误。该服务使用一个字段来跟踪对单个任务的引用。 OnStart,任务执行单个方法。该方法内部有一个循环,并以可配置的时间间隔调用数据库以监视另一个系统正在执行的工作。

protected override void OnStart(string[] args)
{
    _processorTask = Task.Run(() => StartProcessor());
}

我们偶尔会遇到 Task 终止并记录异常的问题,但现有的管道并没有告诉服务停止,所以我们的服务监视器不知道有什么问题。

起初,我尝试添加只是添加对 Stop() 的调用。

private void StartProcessor()
{
    var processor = new PEMonitoringProcessor(_tokenSource.Token);
    
    try
    {
        // The process loop is in the function. If this method exits, good or bad, the service should stop.
        processor.ProcessRun();
    }
    catch (Exception ex)
    {
        // An exception caught here is most likely fatal. Log the ex and start the service shutdown.
        if (log.IsFatalEnabled) { log.Fatal("A fatal error has occurred.", ex); };
    }
    finally
    {
        Stop();
    }
}

但是,我的一位开发人员在 OnStop 方法中注意到,令牌用于指示任务停止,然后等待。如果 Task 调用 Stop 并等待 Stop 到 return,而 OnStop 正在等待 Task 结束,则这对于此代码来说不是好兆头。

protected override void OnStop()
{
    _tokenSource.Cancel();

    try
    {
        _processorTask.Wait();
    }
    // logging & clean-up... 
}

我考虑过一个单独的任务,它不会被 OnStop 等待,它会检查第一个任务的状态,并在第一个任务完成、出现故障等情况下调用停止,但这似乎有点奇怪。我还考虑过引发事件并尝试像 BeginInvoke 这样的方法。

有意停止服务工作正常,因为 OnStop 通过令牌发出正在关闭的信号。我试图涵盖 Task 方法 returns 或意外抛出的可能性,我希望服务停止而不是变成僵尸。

我看到的最直接的方式是这样的:

protected override void OnStart(string[] args) {
    _processorTask = Task.Run(() => StartProcessor());
    _processorTask.ContinueWith(x => {
        // x.Exception contains exception if any, maybe log it here
        Stop();
    }, TaskContinuationOptions.NotOnCanceled);
}

protected override void OnStop() {
    //or !_processorTask.IsCompleted && !_processorTask.IsCanceled && !_processorTask.IsFaulted
    if (_processorTask.Status == TaskStatus.Running) {
        // only cancel and wait if still running. Won't be the case if service is stopping from ContinueWith above
        _tokenSource.Cancel();
        _processorTask.Wait(); 
    }
}

同样的替代方法:

protected override async void OnStart(string[] args) {
    _processorTask = Task.Run(() => StartProcessor());
    bool cancelled = false;
    try {
        await _processorTask;
    }
    catch (OperationCanceledException) {
        // cancelled
        cancelled = true;
    }
    catch (Exception ex) {
        // log it?
    }

    if (!cancelled)
        Stop();
}

// OnStop stays the same