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

拈花ヽ惹草 提交于 2019-12-09 08:08:58

问题


Can anyone explain why this finally block is not executed? I have read posts about when to expect finally block not be executed, but this seems to be another case. This code needs TopShelf and log4net. I am running .net 4.5

I guess it must be the Windows Service engine that kicks in on unhandled exceptions, but why is it running before the finally block has finished?

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

outputs

starting
throwing
stopping

EDIT: Please comment why you are downgrading, maybe you don't understand the problem? I see a big problem here. You write some domain logic that does important stuff in the finally clause on Exception. Then if you host the logic in a Windows Service the design suddenly is broken.


回答1:


From 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

This is by design, .NET has chosen to terminate your application, reason is, there is something terribly wrong, something didn't work as expected, by calling finally, we don't want to do more damage, so best is to end the application.

What if finally throws one more exception, where does that go? If application is about to close, it may have closed or started closing managed resources and accessing them for logging in finally could turn out to be fatal as well.




回答2:


Have you made sure that the logger is getting a chance to flush to disk before the logger is destroyed when the service stops?

Edit

When a service starts it happens on a new thread. Within the Topshelf code there is an AppDomain.CurrentDomain.UnhandledException += CatchUnhandledException; handler.

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

This catches the unhandled exception, and stops the service by setting the manualresetevent (this is the only thing that is blocking the service from ending).

After sleep is called, the thread is signalled and your finally block, which is on the service thread is killed.

The code then exits.

This is wired up in the Run() method in ConsoleRunHost.

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

There is no guarantee that finally will be called for certain exceptions.




回答3:


Since this program runs as a Windows service it is managed by Windows. Windows detects that something went wrong because of the ApplicationException call and it sends Stop to the service which abort the thread before the finally block is executed.

The "finally" block is never executed because Windows pulls the rug from under. This is pefectly logical when you remind how exception handling works :

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

Since you didn't provide a catch block the ApplicationException is propagated up to the other layers and ultimately to Windows service management which handle it by sending the stop request thus aborting the thread.

Side notes :

  • Unmanaged exception in a service is very bad : obviously you should add a catch block and log exceptions.
  • Normally the Stop function is used to tell the working thread it needs to stop. This will give the thread a chance to stop in clean way. Here is a good example.

Edit :

Here is a sample of what I would do. It is more like pseudo-code but you should get the idea.

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



回答4:


Sorry about this being an answer, but couldn't comment. I couldn't find anything specific about the windows service, but I'm assuming it uses background/foreground threading to execute the code.

And in terms of threading, the finally block is sometimes voided (if the thread is aborted or interrupted unexpectedly) - http://blog.goyello.com/2014/01/21/threading-in-c-7-things-you-should-always-remember-about/

Or for a more official post - (Look for the foreground/background threading section) https://msdn.microsoft.com/en-us/library/orm-9780596527570-03-19.aspx

Hopefully it helps you a little




回答5:


The linked article explains why the finally block of a method run into a windows service provided by TopShelf library that raises an unhandled exception, it isn't executed: https://lowleveldesign.wordpress.com/2012/12/03/try-finally-topshelf-winsvc/

The problem seems related to a portion of code in the topshelf library that sleeps the thread that has raised the exception.

Follows an excerpt of the code responsible for the sleep call on the thread, this method belongs to TopShelf library

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


来源:https://stackoverflow.com/questions/31118912/finally-is-not-executed-when-in-a-thread-running-in-a-windows-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!