c# - subprocesos - que son los threads
Finalmente no se ejecuta cuando se está ejecutando un subproceso en un servicio de Windows (5)
¿Alguien puede explicar por qué este bloque finalmente no se ejecuta? He leído publicaciones sobre cuándo esperar finalmente que el bloque no se ejecute, pero este parece ser otro caso. Este código necesita TopShelf y log4net. Estoy corriendo .net 4.5
Supongo que debe ser el motor del Servicio de Windows el que inicia las excepciones no manejadas, pero ¿por qué se está ejecutando antes de que el bloque finalmente haya finalizado?
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");
}
}
}
}
salidas
starting
throwing
stopping
EDITAR: Por favor comente por qué está bajando de categoría, tal vez no entiende el problema? Veo un gran problema aquí. Escribe alguna lógica de dominio que hace cosas importantes en la cláusula finally de Excepción. Entonces, si aloja la lógica en un Servicio de Windows, el diseño se rompe de repente.
¿Se ha asegurado de que el registrador tenga la oportunidad de descargarse en el disco antes de que se destruya cuando el servicio se detiene?
Editar
Cuando se inicia un servicio sucede en un nuevo hilo. Dentro del código de Topshelf hay una AppDomain.CurrentDomain.UnhandledException + = CatchUnhandledException; entrenador de animales.
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));
}
}
Esto detecta la excepción no controlada y detiene el servicio al configurar el evento manual (esto es lo único que impide que el servicio finalice).
Después de llamar al modo de suspensión, se señala el subproceso y se cancela su bloque finally, que está en el subproceso de servicio.
El código luego sale.
Esto está conectado en el método Run () en 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;
}
No hay garantía de que finalmente se llamará para ciertas excepciones.
Dado que este programa se ejecuta como un servicio de Windows, es administrado por Windows. Windows detecta que algo salió mal debido a la llamada de ApplicationException
y envía Stop
al servicio que aborta el hilo antes de que se ejecute el bloque finally
.
El bloque "finalmente" nunca se ejecuta porque Windows tira de la alfombra desde abajo . Esto es perfectamente lógico cuando recuerda cómo funciona el manejo de excepciones:
try {
// Do stuff
} catch (Exception e) {
// Executed first
} finally {
// Executed last
}
Dado que no proporcionó un bloque de catch
, la excepción ApplicationException
se propaga a las otras capas y, en última instancia, a la administración de servicios de Windows que lo maneja al enviar la solicitud de stop
tanto, abortar el hilo.
Notas al margen:
- La excepción no administrada en un servicio es muy mala: obviamente debe agregar un bloque
catch
y registrar excepciones. - Normalmente, la función
Stop
se utiliza para indicar al subproceso de trabajo que necesita detener. Esto le dará al hilo la oportunidad de detenerse de manera limpia. Aquí hay un buen ejemplo .
Editar:
Aquí hay una muestra de lo que haría. Es más como un pseudocódigo, pero deberías tener una 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 Exception || 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();
}
}
Desde MDSN try-finally (Referencia C #)
Dentro de una excepción manejada , se garantiza que el bloque finalmente asociado se ejecute . Sin embargo, si la excepción no se maneja, la ejecución del bloque finally depende de cómo se desencadena la operación de desenrollamiento de la excepción. Eso, a su vez, depende de cómo esté configurada su computadora. Para obtener más información, consulte Procesamiento de excepciones no manejadas en el CLR .
Por lo general, cuando una excepción no controlada finaliza una aplicación, no importa si el último bloque se ejecuta o no.
Esto es por diseño, .NET ha elegido terminar su aplicación, la razón es, hay algo terriblemente mal, algo no funcionó como se esperaba, al llamar finalmente, no queremos hacer más daño, así que lo mejor es terminar. la aplicación.
¿Qué pasa si finalmente lanza una excepción más, a dónde va eso? Si la aplicación está a punto de cerrarse, es posible que haya cerrado o iniciado el cierre de los recursos administrados y el acceso a ellos para iniciar sesión finalmente podría resultar fatal también.
El artículo vinculado explica por qué el bloque final de un método se ejecuta en un servicio de Windows proporcionado por la biblioteca TopShelf que genera una excepción no controlada, no se ejecuta: https://lowleveldesign.wordpress.com/2012/12/03/try-finally-topshelf-winsvc/
El problema parece estar relacionado con una parte del código en la biblioteca de topshelf que duerme el hilo que ha generado la excepción.
Sigue un extracto del código responsable de la llamada de reposo en el hilo, este método pertenece a la biblioteca 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));
}
...
Lamento que esto sea una respuesta, pero no pude comentar. No pude encontrar nada específico sobre el servicio de Windows, pero supongo que utiliza subprocesos de fondo / primer plano para ejecutar el código.
Y en términos de subprocesos, el bloque finally a veces se anula (si el subproceso se anula o se interrumpe inesperadamente) - http://blog.goyello.com/2014/01/21/threading-in-c-7-things-you-should-always-remember-about/
O para una publicación más oficial - (Busque la sección de subprocesos en primer plano / fondo) https://msdn.microsoft.com/en-us/library/orm-9780596527570-03-19.aspx
Esperemos que te ayude un poco.