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

时间:2015-06-29 14:55:17

标签: c# timer windows-services try-catch-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服务中托管逻辑,则设计突然中断。

5 个答案:

答案 0 :(得分:7)

来自MDSN try-finally (C# Reference)

  

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

     

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

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

如果再次抛出一个例外,那会怎么样?如果应用程序即将关闭,它可能已关闭或开始关闭托管资源并访问它们以便最终登录也可能会致命。

答案 1 :(得分:2)

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

修改

当服务启动时,它会在新线程上发生。在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块。

然后退出代码。

这是在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;
    }

无法保证最终会被调用某些例外。

答案 2 :(得分:2)

由于此程序作为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 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();
  }
}

答案 3 :(得分:1)

很抱歉这是一个答案,但无法发表评论。 我找不到任何关于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

希望它可以帮到你一点

答案 4 :(得分:1)

链接文章解释了为什么方法的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));
    }
    ...