Наконец, не выполняется, когда в потоке, запущенном в службе Windows

Может кто-нибудь объяснить, почему этот блок 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

EDIT: Пожалуйста, прокомментируйте, почему вы понижаете рейтинг, возможно, вы не понимаете проблему? Я вижу здесь большую проблему. Вы пишете некоторую логику домена, которая делает важные вещи в предложении finally в Exception. Тогда, если вы используете логику в службе Windows, дизайн внезапно нарушается.

Ответ 1

Из MDSN try-finally (ссылка С#)

В обработанном исключении гарантированно будет выполняться связанный блок finally . Однако, если исключение необработанно, выполнение блока finally зависит от того, как запускается операция отмены разблокировки. Это, в свою очередь, зависит от того, как настроен ваш компьютер. Для получения дополнительной информации см. Обработка необработанных исключений в среде CLR.

Обычно, когда необработанное исключение заканчивается приложением, независимо от того, выполняется или нет блок finally,

Это по дизайну,.NET решил прекратить ваше приложение, причина в том, что что-то ужасно неправильно, что-то не работает должным образом, позвонив наконец, мы не хотим наносить больше урона, поэтому лучше заключается в том, чтобы закончить приложение.

Что, если, наконец, выкинет еще одно исключение, куда он уйдет? Если приложение закрывается, возможно, оно закрыло или запустило закрытые управляемые ресурсы, и доступ к ним для входа в систему, наконец, может оказаться фатальным.

Ответ 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 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));
        }
    }

Это улавливает необработанное исключение и останавливает службу, устанавливая метод handresetevent (это единственное, что блокирует завершение службы).

После того как сон вызывается, поток сигнализируется, и ваш блок finally, находящийся в служебном потоке, будет убит.

Затем код завершается.

Это связано с методом Run() в 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;
    }

Нет гарантии, что в конечном итоге будут вызваны определенные исключения.

Ответ 3

Поскольку эта программа работает как служба 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();
  }
}

Ответ 4

Извините, что это был ответ, но не смог прокомментировать. Я не мог найти ничего конкретного о службе 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

Надеюсь, это поможет вам немного

Ответ 5

Связанная статья объясняет, почему окончательный блок метода запускается в службу Windows, предоставляемую библиотекой TopShelf, которая вызывает необработанное исключение, она не выполняется: 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));
    }
    ...