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

≯℡__Kan透↙ 提交于 2019-12-03 10:23:49

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.

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.

ForguesR

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

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

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