重新启动具有依赖服务的服务

本文关键字:服务 依赖 重新启动 | 更新日期: 2023-09-27 18:10:16

从csharp-example开始并适当注意相关SO问题(从c#重新启动windows服务并且不能重新启动服务)以及与重新启动一个服务有关的各种其他问题,我想知道重新启动服务的最佳方法是依赖服务(例如Message Queuing,依赖于Message Queuing Triggers,或IIS,依赖于FTP PublishingWorld Wide Web Publishing)。mmc管理单元可以自动完成此操作,但代码似乎没有提供相同的功能(至少不那么容易)。

MSDN关于Stop的文档说"如果任何服务的操作依赖于此服务,它们将在此服务停止之前被停止。"DependentServices属性包含依赖于此属性的服务集,而DependentServices返回一个服务数组。假设StartService()StopService()遵循上述示例中概述的约定(除了它们直接接受ServiceControllersTimeSpans),我从以下开始:

public static void RestartServiceWithDependents(ServiceController service, TimeSpan timeout)
{
    ServiceController[] dependentServices = service.DependentServices;
    RestartService(service, timeout); // will stop dependent services, see note below* about timeout...
    foreach (ServiceController dependentService in dependentServices)
    {
        StartService(dependentService, timeout);
    }
}

但是如果服务依赖是嵌套的(递归的)或循环的(如果这是可能的…)-如果Service A依赖于 Service B1Service B2Service C1 依赖于 Service B1,似乎"重启"Service A通过这种方法会停止Service C1,但不会重新启动它…

为了使这个示例更清晰,我将遵循services mmc管理单元中的模型:

The following system components depend on [Service A]:
  - Service B1
    - Service C1
  - Service B2

是否有更好的方法来解决这个问题,或者它只需要递归地进入并停止每个依赖的服务,然后在重新启动主服务后重新启动它们?

另外,依赖的当前已停止的服务会列在DependentServices下吗?如果是这样,这难道不会重启他们吗?如果是这样,我们也应该控制吗?这似乎变得越来越乱…

*注意:我意识到timeout没有被完全正确地应用在这里(整体超时可能比预期的长很多很多倍),但现在这不是我关心的问题-如果你想修复它,很好,但不要只是说'超时坏了…'

更新:经过一些初步测试,我发现(/确认)以下行为:

  • 停止其他服务(如Service B1)依赖的服务(如Service A)将停止其他服务(包括"嵌套"依赖项,如Service C1)
  • DependentServices 确实包含所有状态下的依赖服务(Running, Stopped等),并且它还包括嵌套依赖项,即Service_A.DependentServices将包含{Service B1, Service C1, Service B2}(按此顺序,C1依赖于B1)。
  • 启动依赖于其他服务的服务(例如Service B1 依赖于 Service A)也会启动必需的服务。

上面的代码因此可以简化(至少部分简化),只停止主服务(这将停止所有依赖服务),然后重新启动最依赖的服务(例如Service C1Service B2)(或者只是重新启动"所有"依赖服务-它将跳过已经启动的服务),但这实际上只是暂时推迟主服务的启动,直到其中一个依赖抱怨,所以这并没有真正帮助。

现在看起来只是重新启动所有的依赖是最简单的方法,但这忽略了(目前)管理已经停止的服务等…

重新启动具有依赖服务的服务

好了,终于实现了。我已经把它作为一个单独的答案发布了,因为我已经在我的问题的原始更新中得出了这个结论,这是在第一个答案之前发布的。

再次,StartService(), StopService()RestartService()方法遵循示例中概述的约定以及问题本身已经引用的约定(即它们包装启动/停止行为以避免"已经启动/停止"类型的异常),并补充说,如果传入Service(如下所示),则在检查其Status之前在该服务上调用Refresh()

public static void RestartServiceWithDependents(ServiceController service, TimeSpan timeout)
{
    int tickCount1 = Environment.TickCount; // record when the task started
    // Get a list of all services that depend on this one (including nested
    //  dependencies)
    ServiceController[] dependentServices = service.DependentServices;
    // Restart the base service - will stop dependent services first
    RestartService(service, timeout);
    // Restore dependent services to their previous state - works because no
    //  Refresh() has taken place on this collection, so while the dependent
    //  services themselves may have been stopped in the meantime, their
    //  previous state is preserved in the collection.
    foreach (ServiceController dependentService in dependentServices)
    {
        // record when the previous task "ended"
        int tickCount2 = Environment.TickCount;
        // update remaining timeout
        timeout.Subtract(TimeSpan.FromMilliseconds(tickCount2 - tickCount1));
        // update task start time
        tickCount1 = tickCount2;
        switch (dependentService.Status)
        {
            case ServiceControllerStatus.Stopped:
            case ServiceControllerStatus.StopPending:
                // This Stop/StopPending section isn't really necessary in this
                //  case as it doesn't *do* anything, but it's included for
                //  completeness & to make the code easier to understand...
                break;
            case ServiceControllerStatus.Running:
            case ServiceControllerStatus.StartPending:
                StartService(dependentService, timeout);
                break;
            case ServiceControllerStatus.Paused:
            case ServiceControllerStatus.PausePending:
                StartService(dependentService, timeout);
                // I don't "wait" here for pause, but you can if you want to...
                dependentService.Pause();
                break;
        }
    }
}

请注意,ServiceController.Stop()停止'依赖'服务,ServiceController.Start()启动'依赖'服务-因此,在停止服务后,你只需要启动依赖树的叶子服务。

假设不允许循环依赖,下面的代码获得需要启动的服务:

    private static void FillDependencyTreeLeaves(ServiceController controller, List<ServiceController> controllers)
    {
        bool dependencyAdded = false;
        foreach (ServiceController dependency in controller.DependentServices)
        {
            ServiceControllerStatus status = dependency.Status;
            // add only those that are actually running
            if (status != ServiceControllerStatus.Stopped && status != ServiceControllerStatus.StopPending)
            {
                dependencyAdded = true;
                FillDependencyTreeLeaves(dependency, controllers);
            }
        }
        // if no dependency has been added, the service is dependency tree's leaf
        if (!dependencyAdded && !controllers.Contains(controller))
        {
            controllers.Add(controller);
        }
    }

和一个简单的方法(如扩展方法):

    public static void Restart(this ServiceController controller)
    {
        List<ServiceController> dependencies = new List<ServiceController>();
        FillDependencyTreeLeaves(controller, dependencies);
        controller.Stop();
        controller.WaitForStatus(ServiceControllerStatus.Stopped);
        foreach (ServiceController dependency in dependencies)
        {
            dependency.Start();
            dependency.WaitForStatus(ServiceControllerStatus.Running);
        }
    }

你可以简单地重启一个服务:

    using (ServiceController controller = new ServiceController("winmgmt"))
    {
        controller.Restart();
    }

兴趣点:

为了代码清晰,我没有添加:

  • 超时
  • 错误检查

请注意,应用程序可能会在一个奇怪的状态结束,当一些服务重新启动,而另一些没有…

听起来你想要重新启动一个"基础"服务,并让所有依赖于它的东西也重新启动。如果是这样,就不能重新启动所有依赖服务,因为它们可能事先没有运行。据我所知,没有这样的API。

我的方法是写一个递归函数来扫描所有的依赖服务(和它们的依赖关系),并将所有运行的服务按顺序添加到一个列表中。

当您重新启动基本服务时,您可以运行此列表并启动所有内容。如果您没有重新排序列表,那么服务应该以正确的顺序启动,并且一切都会很好。