Наконец, не выполняется, когда в потоке, запущенном в службе 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));
}
...