Windows服务持续运行的方法

60

我创建了一个名为 ProxyMonitor 的Windows服务,目前服务已安装并且卸载顺利。

现在我这样执行应用程序:

C:\\Windows\\Vendor\\ProxyMonitor.exe /install

非常容易理解,我接着到了 services.msc 并开始服务,但是当我这么做时,我收到以下消息:

本地计算机上的代理监视器服务已启动,然后停止。如果没有工作可做,某些服务会自动停止,例如性能日志和警报服务。

我的代码如下:

public static Main(string[] Args)
{
    if (System.Environment.UserInteractive)
    {
        /*
            * Here I have my install logic
        */
    }
    else
    {
        ServiceBase.Run(new ProxyMonitor());
    }
}

然后在ProxyMonitor类中,我有:

public ProxyMonitor()
{
}

protected override void OnStart(string[] args)
{
    base.OnStart(args);
    ProxyEventLog.WriteEntry("ProxyMonitor Started");

    running = true;
    while (running)
    {
        //Execution Loop
    }
}

onStop() 中,我只是将 running 变量更改为 false

我需要做什么才能使服务始终处于活动状态?因为我需要监视网络并跟踪更改等。


更新:1

protected override void OnStart(string[] args)
{
     base.OnStart(args);
     ProxyEventLog.WriteEntry("ProxyMonitor Started");

     Thread = new Thread(ThreadWorker);
     Thread.Start();
 }

ThreadWorker 中,我有一个 ProxyEventLogger.WriteEntry("Main thread entered"),但它没有触发。

6个回答

150

OnStart()回调需要及时返回,因此您需要启动一个线程来执行所有工作。我建议将以下字段添加到您的类中:

using System.Threading;
private ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
private Thread _thread;

_thread字段将保存在OnStart()回调函数中创建的System.Threading.Thread对象的引用。 _shutdownEvent字段保存了一个系统级事件构造,将用于在服务停止时向线程发出停止运行的信号。

OnStart()回调函数中,创建并启动您的线程。

protected override void OnStart(string[] args)
{
     _thread = new Thread(WorkerThreadFunc);
     _thread.Name = "My Worker Thread";
     _thread.IsBackground = true;
     _thread.Start();
}

为了使其工作,你需要一个名为WorkerThreadFunc的函数。它必须匹配System.Threading.ThreadStart委托签名。

private void WorkerThreadFunc()
{
}

如果在这个函数里什么也不放,线程会启动然后立即关闭,因此你必须在函数中放入一些逻辑,基本上让线程活着,同时你可以做你的工作。这就是_shutdownEvent非常有用的地方。

private void WorkerThreadFunc()
{
    while (!_shutdownEvent.WaitOne(0)) {
        // Replace the Sleep() call with the work you need to do
        Thread.Sleep(1000);
    }
}

这个 while 循环检查 ManualResetEvent 是否被设置。由于我们在上文中将它初始化为 false,所以这个检查会返回 false。在循环内部,我们睡眠了 1 秒钟。你需要用你需要执行的工作替换掉这里的睡眠 - 监视代理设置等。

最后,在你的 Windows 服务的 OnStop() 回调函数中,你需要使用 _shutdownEvent 来通知线程停止运行。

protected override void OnStop()
{
     _shutdownEvent.Set();
     if (!_thread.Join(3000)) { // give the thread 3 seconds to stop
         _thread.Abort();
     }
}

4
很高兴能够帮忙。就我所知,我有几篇详细的 Stack Overflow 教程可供参考,分别介绍了(1)如何在事件查看器中拥有自己的日志记录 (https://dev59.com/cHRB5IYBdhLWcg3wiHv7#593803) 和(2)如何安装/卸载您的服务而不需要使用 InstallUtil.exe 工具 (https://dev59.com/XHM_5IYBdhLWcg3w3nNs#1195621)。 - Matt Davis
@MattDavis 我该如何暂停和继续执行此线程?调用 _shutdownEvent.Set()_shutdownEvent.Reset() 无效。 - My-Name-Is
1
@My-Name-Is,根据示例的组织方式,一旦调用_shutdownEvent.Set(),线程将停止执行。这是因为WorkerThreadFunc()中的while循环将退出。如果您想暂停并稍后恢复同一线程,请创建另一个ManualResetEvent对象来控制线程的这个方面。 - Matt Davis
在OnStop()中,如果线程正在睡眠或等待(参见https://dev59.com/BnNA5IYBdhLWcg3wNa-T),您可能还想调用_thread.Interrupt()。 - user8128167
1
@shaikhspear 是的,本来就应该是这样的。我已经纠正了它。 - Matt Davis
显示剩余7条评论

6
您需要退出您的OnStart处理程序,以便服务控制器意识到您的服务已经启动。为使其按您想要的方式工作,您可以启动一个定时器,在间隔时间内进行处理。 编辑: 尝试在您的OnStart中放置System.Diagnostics.Debugger.Launch()以查看发生了什么(并在ThreadWorker中放置断点)。我建议将此包装在#if DEBUG中,以确保它不会被部署。
我刚刚意识到您没有给您的Thread命名:
 Thread myThread = new Thread(ThreadWorker);
 myThread.Start();

如果有返回代码,比如 return 0return false,那么使用线程也是可以的,对吧? - RobertPitt
不会的,OnStart 事件处理程序返回 void。只要它在合理的时间范围内退出(大约一分钟左右),服务控制器就会很高兴。 - Mark Avenius

3

演示使用控制台应用程序的示例代码。希望这可以帮助你。

 class Program
{
    private static CancellationTokenSource _cancellationTokenSource;
    private static ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
    private static Thread _serviceStartThread;
    private static Thread _serviceStopThread;

    private static int workcounter = 0;
    static void Main(string[] args)
    {

        _cancellationTokenSource = new CancellationTokenSource();
        _serviceStartThread = new Thread(DoWork);
        _serviceStopThread = new Thread(ScheduledStop);
        StartService();
        StopService();
    }

    private static void StartService()
    {
        _serviceStartThread.Start();

    }

    private static void StopService()
    {
        _serviceStopThread.Start();
    }


    /// <summary>
    /// Triggers a cancellation event for stopping the service in a timely fashion.
    /// </summary>
    private static void ScheduledStop()
    {
        while (!_shutdownEvent.WaitOne(0))
        {
            if (workcounter == 10)
            {
                _cancellationTokenSource.Cancel();
            }
        }
    }

    /// <summary>
    /// Represents a long running Task with cancellation option
    /// </summary>
    private static void DoWork()
    {

        while (!_shutdownEvent.WaitOne(0))
        {
            if(!_cancellationTokenSource.Token.IsCancellationRequested)
            {
                workcounter += 1;
                Console.Write(Environment.NewLine);
                Console.Write("Running...counter: " + workcounter.ToString());
                Thread.Sleep(1000);//Not needed, just for demo..
            }
            else
            {
                Console.Write(Environment.NewLine);
                Console.Write("Recieved cancellation token,shutting down in 5 seconds.. counter: " + workcounter.ToString());
                _shutdownEvent.Set();
                Thread.Sleep(5000);//Not needed, just for demo..
            }

        }
    }
}

2
当然不要在OnStart方法中添加while循环。这会告诉操作系统服务未能从OnStart方法安全退出,因此服务未启动。我通常在OnStart方法中创建一个启用的计时器。然后在Ticks方法中调用必要的方法以使应用程序运行。
或者,您可以执行以下操作:
// The main entry point for the process 
static void Main() 
{ 
    System.ServiceProcess.ServiceBase[] ServicesToRun; 
    ServicesToRun = new System.ServiceProcess.ServiceBase[] { new WinService1() }; 
    System.ServiceProcess.ServiceBase.Run(ServicesToRun); 
} 

想要了解有关Windows服务的更多信息,您可以在这里获取一个骨架示例。


0
为什么不在你的解决方案中创建一个类型为Windows服务的新项目呢?这将设置您需要实现的所有结构,包括服务启动/停止事件的处理程序。

1
这部分是一个培训经验,我更愿意从底层开始学习它的实现方式。 - RobertPitt
我认为即使是为了培训(尤其是为了培训?),最好学习一下VS如何创建Windows服务的脚手架,然后再去了解它为什么有效以及如何运作。这比自下而上要好得多,因为你会发现自下而上更加令人沮丧。 - Gabriel Magana
这是事实,虽然有争议,但这不是本案例的情况。该应用程序已完成60%,也许在我的下一个应用程序中,我将从Windows服务模板开始。 - RobertPitt

0
在我的看法中,解决这个问题最简单的方法是:
protected override void OnStart(string[] args)
{            
    new Task(() =>
    {
            new ProxyMonitor();                    
    }).Start();    
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接