繁体   English   中英

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

[英]Finally is not executed when in a Thread running in a Windows Service

任何人都可以解释为什么这个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服务中托管逻辑,则设计突然中断。

来自MDSN try-finally(C#参考)

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

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

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

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

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

编辑

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

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

由于此程序作为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函数用于告知需要停止的工作线程。 这将使线程有机会以干净的方式停止。 这是一个很好的例子

编辑:

这是我要做的一个例子。 它更像伪代码,但你应该得到这个想法。

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

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

在线程方面,finally块有时无效(如果线程被意外中止或中断) - http://blog.goyello.com/2014/01/21/threading-in-c-7-things-you -should-始终记得,大约/

或者更正式的帖子 - (寻找前景/背景线程部分) https://msdn.microsoft.com/en-us/library/orm-9780596527570-03-19.aspx

希望它对你有所帮助

链接的文章解释了为什么方法的finally块遇到由TopShelf库提供的窗口服务引发未处理的异常,它没有被执行: https ://lowleveldesign.wordpress.com/2012/12/03/try- 最后,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));
    }
    ...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM