【问题标题】:C# Windows service OnPreShutdown takes the full 20 secondsC# Windows 服务 OnPreShutdown 需要整整 20 秒
【发布时间】:2020-03-20 06:49:30
【问题描述】:

我编写了一个在本地系统帐户下运行的 C# windows 服务。 当系统关闭时我需要做一些事情,并且挂钩到 pre-shutdown 事件可以正常工作。

我通过设置正确的标志来接受 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);

当我收到 preshutdown 命令时执行我的最后一步。

这行得通,我每次都能执行我的逻辑,我知道 preshutdown 事件给你 20 秒。 我的问题如下:我的代码在不到 1 秒的时间内完成(不需要做太多,我只需要确保我在关闭时这样做),但是,系统仍在等待我的服务完成 20 秒。

通过激活windows 10的“verbose”模式可以看到,可以通过在这个路径添加值为“1”(DWORD值)的“verbosestatus”注册表项来启用: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System

我已经搜索了 2 天,但我似乎无法找到一种方法来告诉系统我的代码已经完成,它不应该等待剩下的约 19 秒,然后就关闭了。

遗憾的是,测试也很耗时,因为您需要关闭机器或 VM 才能捕获此关闭前流程。

有人遇到过这个问题吗?我认为应该有某种取消事件,但我似乎找不到它。

【问题讨论】:

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

标签: c# windows-10 windows-services


【解决方案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 的通知,但您没有做任何事情来更改完成后的服务状态。我仔细研究了 OnCustomCommand 的实现,您使用它来使用 ServiceBase 的反编译副本触发关机。

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。

【讨论】:

    猜你喜欢
    • 2015-03-08
    • 1970-01-01
    • 1970-01-01
    • 2021-07-04
    • 1970-01-01
    • 1970-01-01
    • 2017-01-06
    • 2011-10-31
    • 1970-01-01
    相关资源
    最近更新 更多