【问题标题】:Detect/listen for service start and stop state changes检测/监听服务启动和停止状态变化
【发布时间】:2021-06-28 17:11:15
【问题描述】:

我有一个 C# 应用程序,它使用的 Windows 服务并不总是打开,我希望能够在服务启动和关闭时发送电子邮件通知。我已经编写了电子邮件脚本,但我似乎无法弄清楚如何检测服务状态的变化。

我一直在阅读ServiceController 类,我认为WaitForStatus() 方法可能是我需要的,但我无法找到将它用于服务的示例那还没有开始。 编辑:由于WaitForStatus()方法busy-waits,我需要在监听服务启动/停止的同时执行服务运行的程序的其余部分,我不认为这是适合我的方法,除非有人有解决方案,将这种方法与多线程结合使用并且干净高效。

 

更多:

  • 应用程序不会启动该服务 - 应用程序用户将直接从“管理工具”中的“服务”窗口启动该服务。
  • 所使用的服务不是默认的 Windows 服务 - 它是设计用于此应用程序的自定义服务

 

感谢您的帮助!

 

附:请注意,我对 C# 还很陌生,我在这里边学习边学习。

 

更新:

我已经设法在每次服务启动时发送警报电子邮件:当我继续阅读我拥有的代码时(很遗憾,我无法在此处发布),我注意到用于创建服务正在扩展 ServiceBase 类,并且有人制作了自定义 OnStart() 方法来覆盖默认方法。我向新的OnStart() 方法添加了必要的方法调用,它成功发送了通知。

我尝试对 OnStop() 方法做同样的事情,但这对我来说效果并不好 - 在我继续之前,我想补充一点,我已经用 Java 编程了好几年了,我我非常熟悉Java设计模式。

我试图做的,这本来可以在 Java 中工作,是用一个调用电子邮件通知的方法覆盖 ServiceBase 类的 OnStop() 方法,将 MyService 转换为 ServiceBase 类型,然后重新调用ServiceBase 类的Stop() 方法(注意:OnStop() 是一个受保护的方法,因此不能直接调用它——Stop() 方法调用OnStop(),然后继续使用必要的代码来停止服务)。我认为强制转换为ServiceBase 类型会强制调用默认的OnStop() 方法,而不是我的自定义方法。

正如您想象的那样,在我设法强制我的计算机硬关机之前,我最终成功发送了不到 10,000 封电子邮件到我的收件箱。

我现在需要的是一种方法来使用我重写的OnStop() 方法,然后让它调用默认方法,或者解决这个问题的另一种方法。非常感谢任何和所有帮助。非常感谢。

 

对于那些拥有多线程解决方案的人:

protected override void OnStart(string[] args) {
     string subject = "Notice: Service Started";
     string body = "This message is to notify you that the service " +
         "has been started. This message was generated automatically.";
     EmailNotification em = new EmailNotification(subject, body);
     em.SendNotification();

     ...INITIALIZE LISTENER FOR SERVICE STOPPING HERE...

     ...custom stuff to be run on start...
 }

另外,请记住调用此方法的类,我们称之为Service,扩展了ServiceBase 类。

 

更新二:

关于我使用NotifyServerStatusChange的建议,我了解到由于各种原因,该解决方案不允许使用系统功能。澄清一下,只有纯粹在 C# 和 .NET 范围内的解决方案才是可行的。再次感谢您的帮助!

【问题讨论】:

  • 对,但是应用程序没有检查事件的状态 - 它需要不断地监听状态变化
  • @Chriseyre2000:这是一个有效的解决方案,也是迄今为止我唯一拥有的解决方案。如果您想删除评论并使其成为答案,我会投票赞成,因为这是对 SO 的唯一答案。但是,正如文章本身所说,代码最终会变得混乱,我真的不能朝那个方向发展。
  • @Peter Ritchie:你所说的问题是重复的,它所链接的问题的答案适用于 C++ 而不是 C#。如果您愿意查看这些问题引用的文档,您会发现即使在 Microsoft 文档中也只显示或引用了 C++ 代码。我已经查找了 notifyservicestatuschange,但尚未在 C# 中使用该方法的任何地方找到一个示例。现在,如果此功能在 C# 中确实有效,请随时将其发布为答案,如果有效,我将升级并接受它。
  • @ZacharyKniebel 你可以通过 PInvoke 访问 C# 中的所有 Win32 函数,任何 Win32 C++ 都可以是 C#,如果你愿意的话,请努力。

标签: c# asp.net service event-handling


【解决方案1】:

这是解决方案以及我之前找不到的原因:正如我之前所说,我的类扩展了 ServiceBase 类。在我的第一次更新中,我发布了我试图以与使用 Java 解决它相同的方式来解决这个问题:通过强制转换。但是,在 C# 中,如果您在派生类中覆盖它,则显然不允许您调用基方法。当我第一次发布这个问题和这个更新时,我不知道的一件事(显然是没有人想到的一件事)是 C# 包含可用于调用基础方法的 base 构造函数派生类的类。由于base 构造函数可用于C# 中的任何类,它不会出现在ServiceBase Class documentation 中。

一旦我学会了这一点,我就可以采用我原来的解决方案并修改它以使用基类:

    protected override void OnStop() {
        string subject = "Notice: Service Stopped";
        string body = "This message is to notify you that the service has " +
            "been stopped. This message was generated automatically.";
        EmailNotification em = new EmailNotification(subject, body);
        em.SendNotification();
        base.OnStop();
    }

当我在 Visual Studio 中玩代码并注意到我的 IntelliSense 中的 base 时,我发现了这一点。我点击进入它的定义,它把我送到了 ServiceBase(显然没有定义)。在注意到我的代码中没有定义 base 并且它是 ServiceBase 类的一个实例之后,我意识到它一定是某种构造函数。快速谷歌搜索后,我找到了我要找的东西。智能感知的好方法!

感谢大家的帮助!

【讨论】:

    【解决方案2】:

    如果您想要一个没有 win32 api 的 .NET 解决方案,请查看下面我的解决方案。我从 ServiceController 类继承并在 Task 中使用 WaitForStatus() 使其成为非阻塞,然后在状态更改时引发事件。也许它需要更多测试但对我有用:

    类定义

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using System.ServiceProcess; // not referenced by default
    
    public class ExtendedServiceController: ServiceController
    {
        public event EventHandler<ServiceStatusEventArgs> StatusChanged;
        private Dictionary<ServiceControllerStatus, Task> _tasks = new Dictionary<ServiceControllerStatus, Task>();
    
        new public ServiceControllerStatus Status
        {
            get
            {
                base.Refresh();
                return base.Status;
            }
        }
    
        public ExtendedServiceController(string ServiceName): base(ServiceName)
        {
            foreach (ServiceControllerStatus status in Enum.GetValues(typeof(ServiceControllerStatus)))
            {
                _tasks.Add(status, null);
            }
            StartListening();
        }
    
        private void StartListening()
        {
            foreach (ServiceControllerStatus status in Enum.GetValues(typeof(ServiceControllerStatus)))
            {
                if (this.Status != status && (_tasks[status] == null || _tasks[status].IsCompleted))
                {
                    _tasks[status] = Task.Run(() =>
                    {
                        try
                        {
                            base.WaitForStatus(status);
                            OnStatusChanged(new ServiceStatusEventArgs(status));
                            StartListening();
                        }
                        catch
                        {
                            // You can either raise another event here with the exception or ignore it since it most likely means the service was uninstalled/lost communication
                        }
                    });
                }
            }
        }
    
        protected virtual void OnStatusChanged(ServiceStatusEventArgs e)
        {
            EventHandler<ServiceStatusEventArgs> handler = StatusChanged;
            handler?.Invoke(this, e);
        }
    }
    
    public class ServiceStatusEventArgs : EventArgs
    {
        public ServiceControllerStatus Status { get; private set; }
        public ServiceStatusEventArgs(ServiceControllerStatus Status)
        {
            this.Status = Status;
        }
    }
    

    用法

    static void Main(string[] args)
    {
        ExtendedServiceController xServiceController = new ExtendedServiceController("myService");
        xServiceController.StatusChanged += xServiceController_StatusChanged;
        Console.Read();
    
        // Added bonus since the class inherits from ServiceController, you can use it to control the service as well.
    }
    
    // This event handler will catch service status changes externally as well
    private static void xServiceController_StatusChanged(object sender, ServiceStatusEventArgs e)
    {
        Console.WriteLine("Status Changed: " + e.Status);
    }
    

    【讨论】:

      【解决方案3】:

      ServiceController 类有一个WaitForStatus 方法。不过,它在内部进行轮询。

      【讨论】:

      • 是的,除非有人能告诉我如何为这项工作创建一个新线程,否则我不会在性能方面花费太多(注意:我一直正在研究这个应用程序的多线程,但是,正如我之前所说,我对 C# 有点陌生,从我读过的内容来看,它听起来不会正常或有效地工作 - 想法?)
      • 你认为一个或几个轮询线程是性能问题吗?它们每 250 毫秒唤醒一次。为什么你会认为这是不行的?
      • 对不起,我认为我的评论不是很清楚 - 我并不是说轮询会成为性能问题,只有第二个线程。就性能而言,轮询本身对我来说完全没问题,但我需要在程序监听服务停止时做一些事情。根据定义,轮询是忙等待,除非我有两个线程,否则我无法做到这一点,这样我就可以一个忙等待,另一个处理程序的其余部分。
      • 好的,你不能再创建一个线程吗?也许我不完全了解您的情况,但我看不出新线程会成为问题的原因。无论是性能方面还是其他方面。如果您关心内存使用情况,您可以减少该线程的堆栈大小。 Thread ctor 接受一个 threadSize 参数。
      • 那太好了。正如我所说,我一直在阅读多线程和创建更多线程,但我仍然不清楚在这种情况下我应该如何创建和删除线程。稍后,我将对问题添加一个编辑,并向您展示我将在哪里(或至少我认为我会)创建新线程。
      【解决方案4】:

      如果您无法 PInvoke NotifyServiceStatusChange,那么您将不得不轮询该服务。例如:

      ServiceController sc = new ServiceController("Some Service");
      Console.WriteLine("Status = " + sc.Status);
      

      【讨论】:

      • 这是一个很好的解决方案,如果我们要实现“心跳监视器”,效果会很好,但是我们的规范要求解决方案是服务本身的一部分,当服务被告知时执行停止,就在它停止之前。
      【解决方案5】:

      您可以使用 wmi 来监控事件:technet.microsoft.com/en-us/library/ff730927.aspx

      【讨论】:

        【解决方案6】:

        使用 NotifyServiceStatusChange() 时要非常小心,因为它仅在 Windows Vista/Windows 2008(及更高版本)上受支持。如果您针对以下任何平台,则无法使用该 API。 (仍然有很多 XP/Windows 2000-2003 系统。)

        更糟糕的是,在服务重新启动时轮询并不总是可靠的,因为如果您在非常快的系统(SSD 驱动器或虚拟机上的预缓冲 I/O)上轮询服务,服务可能会在两次轮询之间重新启动。

        【讨论】:

        • 谢谢 - 我会定期检查堆栈,并总是查看我发布的帖子/问题上的新答案/cmets。您的帖子非常有见地,但更多的是评论而不是解决方案-您是否也有其他解决方案可以建议? (如果没有,请将您的帖子更改为评论而不是答案)谢谢:)
        猜你喜欢
        • 2010-12-22
        • 2017-05-31
        • 1970-01-01
        • 2017-06-24
        • 2021-02-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-16
        相关资源
        最近更新 更多