C# Windows服务的OnPreShutdown方法需要20秒才能完成。

3

我编写了一个在本地系统账户下运行的 C# Windows 服务。

当系统关闭时,我需要执行某些操作,而钩取到预关机事件可以正常工作。

我通过设置正确的标志接受 preshutdown 命令:

FieldInfo acceptedCommandsFieldInfo = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
                if (acceptedCommandsFieldInfo == null)
                {
                    throw new Exception("acceptedCommands field not found");
                }
                int value = (int)acceptedCommandsFieldInfo.GetValue(service);
                acceptedCommandsFieldInfo.SetValue(service, value | SERVICE_ACCEPT_PRESHUTDOWN);

在接收到预关机命令时执行我的最后几步。
这个方案是可行的,我每次都能够执行我的逻辑,我知道预关机事件会给你20秒时间。但我的问题是:我的代码在不到1秒钟内就完成了(没有做多少事情,只需确保我在关闭时这样做),但是系统仍在等待我的服务完成20秒钟。
通过激活Windows 10的“详细信息”模式,可以看到这一点,方法是在以下路径添加一个名为“verbosestatus”的DWORD值为“1”的注册表项: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
我已经搜索了2天,似乎找不到一种方法告诉系统我的代码已经完成,不需要等待剩余的约19秒钟,直接关机即可。
遗憾的是,这也很耗时,因为您需要关闭计算机或虚拟机才能捕获此预关机流程。
是否有人遇到过这个问题?我想应该有某种取消事件,我只是似乎找不到它。

新用户发布了问题,如果您需要任何额外的答案,请告诉我。 - Darksody
你是否启动了任何线程?在创建时使用 myThread.IsBackground = true 进行标记可能会有所帮助。如果这不能解决你的问题,你需要进行转储。 - Oguz Ozgul
我没有特别启动任何线程,但我正在使用TPL,因此可能有一些正在运行的任务。这些任务确实使用了线程池,不过我会寻找一种让它们在后台运行的方法。 - Darksody
更新:我已经对我的代码进行了注释,所以基本上我只是设置了preshutdown标志,在preshutdown事件上我什么也不做(我写一个成功完成的日志)。没有其他线程或任务参与,我已经注释掉了我的OnStart方法和所有内容。结果相同。只要在那里放置preshutdown标志,就会让Windows等待我的服务20秒钟。 - Darksody
更新2:我创建了一个简单的Windows服务,运行在本地系统下,除了在预关闭时写入文件外什么也不做(我还对代码进行了注释,所以它实际上什么也没做,但我得到了相同的响应)。我已经将示例代码上传到这里:https://github.com/darksody/PreshutdownService - Darksody
1个回答

1

注意:我不得不从GITHub链接中反编译您的exe,因为您没有包含cs文件。

这是您的代码...

    /// You are calling this in your constructor
    private void RegisterPreShutdownEvent()
    {
        FieldInfo field = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
        if (field == null)
        {
            throw new Exception("acceptedCommands field not found");
        }
        int value = (int)field.GetValue(this);
        field.SetValue(this, value | 256);
    }

    /// And processing the result here
    protected override void OnCustomCommand(int command)
    {
        base.OnCustomCommand(command);
        if (command == 15)
        {
            this.OnPreShutdown();
        }
    }

我认为你的PRESHUTDOWN实现方案不够充分。虽然你已经绑定了事件并在PreShutdown时得到了通知,但是当完成后你没有采取任何措施来改变服务的状态。我仔细研究了你使用ServiceBase的反编译副本触发关闭的OnCustomCommand实现。

private void DeferredCustomCommand(int command)
    {
        try
        {
            this.OnCustomCommand(command);
            this.WriteEventLogEntry("Service command was processed successfully.");
        }
        catch (Exception exception)
        {
            this.WriteEventLogEntry(string.Format("Failed to process service command. {0}", exception), EventLogEntryType.Error);
            throw;
        }
    }

当使用DeferredShutdown()处理正常关闭时,它的流程如下。请注意,它会触发OnShutdown事件,然后检查并设置服务状态。

    private unsafe void DeferredShutdown()
    {
        try
        {
            this.OnShutdown();
            this.WriteEventLogEntry("Service has been successfully shut down.");
            if ((this.status.currentState == SERVICE_PAUSED) || (this.status.currentState == SERVICE_RUNNING))
            {
                fixed (NativeMethods.SERVICE_STATUS* service_statusRef = &this.status)
                {
                    this.status.checkPoint = 0;
                    this.status.waitHint = 0;
                    this.status.currentState = SERVICE_STOPPED;
                    NativeMethods.SetServiceStatus(this.statusHandle, service_statusRef);
                    if (this.isServiceHosted)
                    {
                        try
                        {
                            AppDomain.Unload(AppDomain.CurrentDomain);
                        }
                        catch (CannotUnloadAppDomainException exception)
                        {
                            this.WriteEventLogEntry(string.Format("Failed to unload app domain {0}.  The following exception occurred: {1}.", AppDomain.CurrentDomain.FriendlyName, exception.Message), EventLogEntryType.Error);
                        }
                    }
                }
            }
        }
        catch (Exception exception2)
        {
            this.WriteEventLogEntry(string.Format("Failed to shut down service. The error that occurred was: {0}.", exception2), EventLogEntryType.Error);
            throw;
        }
    }

我没有确切的答案来解决这个问题。但我找到了这个链接:https://www.atomia.com/2011/11/16/how-to-process-the-preshutdown-event-in-a-managed-windows-service/,看起来可能会有用。不同之处在于他们强制调用DeferredShutdown(),这将触发您的服务类的OnShutdown()事件,并在完成时正确更新服务状态。以下是他们解决方案的代码:

    internal virtual int MyServiceControlCallbackEx(int control, int eventType, IntPtr eventData, IntPtr eventContext)
    {
        var baseCallback = typeof(ServiceBase).GetMethod("ServiceCommandCallbackEx", BindingFlags.Instance | BindingFlags.NonPublic);
        switch (control)
        {
            case 0x0000000F: //pre shutdown
                             // now pretend shutdown was called
                return (int)baseCallback.Invoke(this, new object[] { 0x00000005, eventType, eventData, eventContext });
            default:
                // call base
                return (int)baseCallback.Invoke(this, new object[] { control, eventType, eventData, eventContext });
        }
    }

    internal virtual void MyInitialize(bool multipleServices)
    {
        // call base
        var init = typeof(ServiceBase).GetMethod("Initialize", BindingFlags.Instance | BindingFlags.NonPublic);
        init.Invoke(this, new object[] { multipleServices });

        // register our handler
        var targetDelegate =
        typeof(ServiceBase).Assembly.GetType("System.ServiceProcess.NativeMethods+ServiceControlCallbackEx");

        var commandCallbackEx = typeof(ServiceBase).GetField("commandCallbackEx", BindingFlags.Instance | BindingFlags.NonPublic);
        commandCallbackEx.SetValue(this, Delegate.CreateDelegate(targetDelegate, this, "MyServiceControlCallbackEx"));

        // accept preshutdown command
        var acceptedCommands = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
        int accCom = (int)acceptedCommands.GetValue(this);
        acceptedCommands.SetValue(this, accCom | 0x00000100);
    }

要使用它,您需要将这些方法添加到您的服务类中,并从构造函数中调用MyInitialize,然后覆盖OnShutdown()并编写您想要执行的代码。我认为他们还建议您在服务类中将CanShutdown属性设置为false。

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