为什么在 Topshelf 中多次调用 Stop 方法

Why Stop method is invoked many times in Topshelf

Topshelf 在我们的应用程序中充当 windows 服务代理。今天早上,我们发现 Stop 方法被调用了很多次。这是相关的代码。

class Program
{
    static void Main(string[] args)
    {
        ILog Log = new FileLog();
        try
        {
            HostFactory.Run(serviceConfig =>
            {
                serviceConfig.Service<ServiceManager>(serviceInstance =>
                {
                    serviceInstance.ConstructUsing(() => new ServiceManager());
                    serviceInstance.WhenStarted(execute => execute.Start());
                    serviceInstance.WhenStopped(execute => execute.Stop());
                });

            });
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            Log.Error("Program.Main", ex, LogType.Error);
            Console.ReadLine();
        };
    }
}

在 ServiceManager 中,我们有 Stop 方法,当 TopShelf 收到来自操作系统的停止信号时将调用该方法。

class ServiceManager
{

    xxx.....

    public bool Stop()
    {
        try
        {
            _log.Info("The service is stopping", LogType.Info);
            _service.StopExecuteProduceMessage();
            Task.WaitAll(_tasks.ToArray());
            _log.Info("The service is stopped", LogType.Info);
        }
        catch (Exception ex)
        {
            _log.Error("Stop", ex, LogType.Error);
        }
        return true;
    }
}

今天早上,我们发现服务已停止,原因不明。并且有很多行记录了这个停止操作。

我猜 Topshelf 多次调用 ServiceManager.Stop 方法。以前有人遇到过这个问题吗?我想知道我可以追踪为什么会发生这种情况。

有人可以帮忙吗?非常感谢。

您遇到此行为是因为您的 Stop() 方法需要一段时间但没有响应停止请求。

您的方法基本上如下所示:

Stop() {
   log-stopping;
   wait-a-while;
   log-stopped;
}

在您等待期间,服务状态保持 "Running"。这会导致请求者(可能是 Windows 本身或另一个程序)不断重新发送停止请求,从而导致多次 parallel/overlapping 调用 Stop()。这说明了您包含的日志中的前 10 行。

您可以看到 "wait" 需要将近 20 秒才能完成(从 05:39:45 到 05:40:04)。

在那之后,看起来 Topshelf 可能会卡住。这会导致发送更多消息。 (请注意,在日志的下一行中,同时记录了停止和开始对,因为您的任务已停止并且没有等待)。

要解决此问题,您应该:

  1. 修改 WhenStopped() 调用以将 HostControl 参数传递给 Stop():

    serviceInstance.WhenStopped((执行, hostControl) => execute.Stop(hostControl));

  2. 更新 Stop() 方法以获取 HostControl 参数并在调用 Task.WaitAll() 之前进行此调用:

    hostControl.RequestAdditionalTime(TimeSpan.FromSeconds(30));

这将通知 Windows 您的服务已收到请求并且可能正在处理它长达 30 秒。那应该避免重复调用。

参考:Topshelf documentation