在 Windows 服务中的线程 运行 中最终不执行

Finally is not executed when in a Thread running in a Windows Service

谁能解释一下为什么这个 finally 块没有被执行?我读过关于何时期望 finally 块不被执行的帖子,但这似乎是另一种情况。此代码需要 TopShelf 和 log4net。我是 运行 .net 4.5

我想一定是 Windows 服务引擎启动了未处理的异常,但为什么它 运行 在 finally 块完成之前?

using log4net;
using log4net.Config;
using System;
using System.Threading;
using Topshelf;

namespace ConsoleApplication1
{
    public class HostMain
    {
        static void Main(string[] args)
        {
            HostFactory.Run(x =>
            {
                x.Service<HostMain>(s =>
                {
                    s.ConstructUsing(name => new HostMain());
                    s.WhenStarted(tc => tc.Start());
                    s.WhenStopped(tc => tc.Stop());
                });

                x.RunAsLocalSystem();
                x.SetServiceName("TimerTest");
            });
        }

        public void Stop()
        {
            LogManager.GetLogger("MyLog").Info("stopping");
        }

        public void Start()
        {
            XmlConfigurator.Configure();

            LogManager.GetLogger("MyLog").Info("starting");

            new Thread(StartServiceCode).Start();
        }

        public void StartServiceCode()
        {
            try
            {
                LogManager.GetLogger("MyLog").Info("throwing");

                throw new ApplicationException();
            }
            finally
            {
                LogManager.GetLogger("MyLog").Info("finally");
            }
        }
    }
}

产出

starting
throwing
stopping

编辑:请评论你为什么要降级,也许你不明白这个问题?我在这里看到一个大问题。您在 Exception 的 finally 子句中编写了一些域逻辑来做重要的事情。然后,如果您将逻辑托管在 Windows 服务中,设计就会突然崩溃。

很抱歉这是一个答案,但无法发表评论。 我找不到关于 windows 服务的任何具体信息,但我假设它使用 background/foreground 线程来执行代码。

并且在线程方面,finally 块有时会无效(如果线程中止或意外中断)- http://blog.goyello.com/2014/01/21/threading-in-c-7-things-you-should-always-remember-about/

或更官方的 post -(查找 foreground/background 线程部分) https://msdn.microsoft.com/en-us/library/orm-9780596527570-03-19.aspx

希望对你有所帮助

您是否确保在服务停止时记录器被销毁之前记录器有机会刷新到磁盘?

编辑

当服务启动时,它发生在一个新线程上。在 Topshelf 代码中有一个 AppDomain.CurrentDomain.UnhandledException += CatchUnhandledException;处理程序。

    void CatchUnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        _log.Fatal("The service threw an unhandled exception", (Exception)e.ExceptionObject);

        HostLogger.Shutdown();

        if (e.IsTerminating)
        {
            _exitCode = TopshelfExitCode.UnhandledServiceException;
            _exit.Set();

#if !NET35
            // it isn't likely that a TPL thread should land here, but if it does let's no block it
            if (Task.CurrentId.HasValue)
            {
                return;
            }
#endif

            // this is evil, but perhaps a good thing to let us clean up properly.
            int deadThreadId = Interlocked.Increment(ref _deadThread);
            Thread.CurrentThread.IsBackground = true;
            Thread.CurrentThread.Name = "Unhandled Exception " + deadThreadId.ToString();
            while (true)
                Thread.Sleep(TimeSpan.FromHours(1));
        }
    }

这会捕获未处理的异常,并通过设置 manualresetevent 停止服务(这是唯一阻止服务结束的事情)。

调用 sleep 后,线程会收到信号,服务线程上的 finally 块会被终止。

代码然后退出。

这是在 Console运行Host 的 运行() 方法中连接的。

    public TopshelfExitCode Run()
    {
        Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);

        AppDomain.CurrentDomain.UnhandledException += CatchUnhandledException;

        if (_environment.IsServiceInstalled(_settings.ServiceName))
        {
            if (!_environment.IsServiceStopped(_settings.ServiceName))
            {
                _log.ErrorFormat("The {0} service is running and must be stopped before running via the console",
                    _settings.ServiceName);

                return TopshelfExitCode.ServiceAlreadyRunning;
            }
        }

        bool started = false;
        try
        {
            _log.Debug("Starting up as a console application");
            _log.Debug("Thread.CurrentThread.Name");
            _log.Debug(Thread.CurrentThread.Name);
            _exit = new ManualResetEvent(false);
            _exitCode = TopshelfExitCode.Ok;

            Console.Title = _settings.DisplayName;
            Console.CancelKeyPress += HandleCancelKeyPress;

            if (!_serviceHandle.Start(this))
                throw new TopshelfException("The service failed to start (return false).");

            started = true;

            _log.InfoFormat("The {0} service is now running, press Control+C to exit.", _settings.ServiceName);

            _exit.WaitOne();
        }
        catch (Exception ex)
        {
            _log.Error("An exception occurred", ex);

            return TopshelfExitCode.AbnormalExit;
        }
        finally
        {
            if (started)
                StopService();

            _exit.Close();
            (_exit as IDisposable).Dispose();

            HostLogger.Shutdown();
        }

        return _exitCode;
    }

不能保证 finally 会在某些异常情况下被调用。

由于此程序作为 Windows 服务运行,因此它由 Windows 管理。 Windows 检测到由于 ApplicationException 调用出现了错误,它发送 Stop 到服务,该服务在执行 finally 块之前中止线程。

"finally" 块永远不会执行,因为 Windows 从 下方拉出地毯。当您提醒异常处理的工作原理时,这是完全合乎逻辑的:

try {
  // Do stuff
} catch (Exception e) {
  // Executed first
} finally {
  // Executed last
}

由于您没有提供 catch 块,因此 ApplicationException 会传播到其他层并最终传播到 Windows 服务管理,后者通过发送 stop 请求因此中止线程。

旁注:

  • 服务中的非托管异常非常糟糕:显然您应该添加一个 catch 块并记录异常。
  • 通常 Stop 函数用于告诉工作线程它需要停止。这将使线程有机会以干净的方式停止。这里有一个good example.

编辑:

这是我会做的一个例子。它更像是伪代码,但你应该明白了。

public void StartServiceCode(object state)
{
  bool stopTimer = false;
  try
  {
    LogManager.GetLogger("MyLog").Info("Locking");
    lock (thisLock) {
      LogManager.GetLogger("MyLog").Info("Throwing");
      throw new ApplicationException();
    }
  } catch (Exception e) {
    // The lock is relased automatically
    // Logging the error (best practice)
    LogManager.GetLogger("MyLog").Info("Exception occurred...");
    // If severe, we need to stop the timer
    if (e is WhosebugException || e is OutOfMemoryException) stopTimer = true;
  } finally {
    // Always clean up
    LogManager.GetLogger("MyLog").Info("finally");
  }
  // Do we need to stop?
  if (stopTimer) {
    LogManager.GetLogger("MyLog").Info("Severe exception : stopping");
    // You need to keep a reference to the timer. (yes, a timer can stop itself)
    timer.Stop();
  }
}

来自 MDSN try-finally (C# Reference)

Within a handled exception, the associated finally block is guaranteed to be run. However, if the exception is unhandled, execution of the finally block is dependent on how the exception unwind operation is triggered. That, in turn, is dependent on how your computer is set up. For more information, see Unhandled Exception Processing in the CLR.

Usually, when an unhandled exception ends an application, whether or not the finally block is run is not important

这是设计使然,.NET 已选择终止您的应用程序,原因是出现了严重错误,某些内容未按预期工作,通过调用 finally,我们不想造成更多损害,所以最好结束应用程序。

如果最后再抛出一个异常,那会去哪里呢?如果应用程序即将关闭,它可能已经关闭或开始关闭托管资源并访问它们以最终登录也可能是致命的。

链接的文章解释了为什么方法 运行 的 finally 块进入 TopShelf 库提供的 windows 服务,引发未处理的异常,它没有被执行:https://lowleveldesign.wordpress.com/2012/12/03/try-finally-topshelf-winsvc/

该问题似乎与 topshelf 库中使引发异常的线程休眠的部分代码有关。

下面是线程上负责sleep调用的代码摘录,该方法属于TopShelf库

    ...
    void CatchUnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        _log.Error("The service threw an unhandled exception", (Exception)e.ExceptionObject);

        ...

        int deadThreadId = Interlocked.Increment(ref _deadThread);
        Thread.CurrentThread.IsBackground = true;
        Thread.CurrentThread.Name = "Unhandled Exception " + deadThreadId.ToString();
        while (true)
            Thread.Sleep(TimeSpan.FromHours(1));
    }
    ...