【问题标题】:Finally is not executed when in a Thread running in a Windows Service在 Windows 服务中运行的线程中时不执行 finally
【发布时间】:2015-09-16 02:34:19
【问题描述】:

谁能解释为什么这个 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

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

【问题讨论】:

  • 你怎么知道它没有执行?您是否尝试过单步执行代码?
  • 日志记录告诉我,如果您使用调试器,也是如此。
  • 它是否进入 finally 块 - 所以不执行。或者可能是不记录的问题。
  • 工作线程中的异常会无条件地终止您的应用程序。不为 AppDomain.CurrentDomain.UnhandledException 编写事件处理程序绝不是错误。
  • 恕我直言,它与 CurrentDomain.UnhandledException 无关。如果我添加这样,这将被执行,但这并不能解释为什么 finally 块没有被执行。

标签: c# timer windows-services try-catch-finally


【解决方案1】:

来自 MDSN try-finally (C# Reference)

在处理的异常中,相关的finally 块保证运行。但是,如果异常未处理,finally 块的执行取决于异常展开操作的触发方式。反过来,这取决于您的计算机的设置方式。如需更多信息,请参阅Unhandled Exception Processing in the CLR

通常,当一个未处理的异常结束一个应用程序时,finally块是否运行并不重要

这是设计使然,.NET 选择终止您的应用程序,原因是,有一些非常错误的东西,没有按预期工作,通过调用 finally,我们不想造成更多损害,所以最好是结束申请。

如果 finally 又抛出一个异常,那会发生什么?如果应用程序即将关闭,它可能已经关闭或开始关闭托管资源并最终访问它们以进行登录也可能是致命的。

【讨论】:

  • 谢谢。所以 lock (obj) { } 也是一个失败的设计,因为这只是 Monitor.Enter / Exit in a try-finally。
  • 并非如此,如果应用程序正在关闭,Monitor 将自动销毁,因此您无需关闭它。操作系统会在进程退出时关闭进程打开的所有锁、资源。
  • 但是后面的所有代码都不能取锁,例如最后执行的 Stop 事件中的代码。
【解决方案2】:

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

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

或者更官方的帖子 - (查找前台/后台线程部分) https://msdn.microsoft.com/en-us/library/orm-9780596527570-03-19.aspx

希望对你有所帮助

【讨论】:

  • 我同意,这与 Windows 服务环境中止的线程有关。但是为什么不允许线程完成它的 finally 块?异常发生在线程内。异常没有发生在线程之外,在任何时候中止线程并完成进程退出都是非常有意义的。
【解决方案3】:

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

编辑

当服务启动时,它发生在一个新线程上。在 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));
        }
    }

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

sleep 被调用后,线程被发出信号并且你的 finally 块,它位于服务线程上。

然后代码退出。

这是在 ConsoleRunHost 的 Run() 方法中连接起来的。

    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 会在某些异常情况下被调用。

【讨论】:

  • 我正在使用具有隐式刷新的 FileAppender。此外,由于 finally 块中的 Monitor.Exit,我最初发现了这种带有死锁的行为。调试器也显示相同,finally 块永远不会执行。
【解决方案4】:

由于此程序作为 Windows 服务运行,它由 Windows 管理。 Windows 检测到由于ApplicationException 调用而出现问题,并在执行finally 块之前将Stop 发送到中止线程的服务。

“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 StackOverflowException || 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();
  }
}

【讨论】:

  • 谢谢。所以 lock (obj) { } 也是一个失败的设计,因为这只是 Monitor.Enter / Exit in a try-finally
  • 我不明白。你对lock也有问题吗?
  • lock 语法只是 try-finally 与 Monitor.Enter / Exit 的语法糖。因此,如果发生异常,则永远不会释放锁。在我的 Windows 服务停止事件中,我使用相同的锁来清理东西,然后发生死锁。
  • 只捕获异常:您将能够记录错误,最终代码将被执行,Windows 不会立即停止服务,lock will be released 您将避免死锁。
  • 在我的情况下记录错误是不够的 - 在大多数情况下我认为。我希望服务停止并重新启动。
【解决方案5】:

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

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

下面是负责线程睡眠调用的代码摘录,该方法属于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));
    }
    ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-07
    • 2018-12-23
    • 1970-01-01
    • 2019-06-06
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    相关资源
    最近更新 更多