es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

¿Cómo provocar un bloqueo en una excepción de tarea no controlada?

Me gustaría comprender correctamente las consecuencias de no observar una excepción lanzada en una Task utilizada de manera “fire and forget” sin manejo de excepciones.

Aquí hay un extracto de CLR via C#, Tercera Edición de Jeffry Richter: “[…] cuando se recolecta un objeto Task, su método Finalize verifica si la Task experimentó una excepción no observada; si lo hizo, el método Finalize de Task lanza [una excepción]. Dado que no puedes capturar una excepción lanzada por el subproceso finalizador del CLR, tu proceso se termina de inmediato.”

Estoy escribiendo un código de prueba para provocar una terminación, pero no logro provocarla.

Utilizando el código de prueba disponible aquí, puedo ver que se llama al controlador TaskScheduler.UnobservedTaskException. Sin embargo, si comento la suscripción del controlador de eventos, la excepción parece ser absorbida y no provoca la terminación del programa.

He probado esto utilizando el Framework .NET en las versiones 4 y 4.8 con una compilación de lanzamiento.

¿Cómo puedo demostrar que no observar una excepción lanzada en una Task realmente provoca un bloqueo?

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Según el libro CLR via C# de Jeffry Richter, no observar una excepción lanzada en una Task utilizada de manera “fire and forget” sin un manejo de excepciones puede resultar en la terminación inmediata del proceso. Sin embargo, según el código de prueba proporcionado, parece que el controlador TaskScheduler.UnobservedTaskException puede capturar la excepción no observada y evitar que el proceso se bloquee.

    Para demostrar que no observar una excepción lanzada en una Task sin manejo de excepciones puede causar un bloqueo, un enfoque posible es crear una Task que lance una excepción sin ser observada o esperada, y luego esperar a que el recolector de basura limpie la tarea. Esto se puede hacer siguiendo los siguientes pasos:

    1. Crear una Task que lance una excepción sin ser observada:
      Task.Run(() => throw new Exception("Excepción no observada"));
      
    2. Agregar una espera para permitir que la Task sea recolectada por el recolector de basura:
      await Task.Delay(TimeSpan.FromSeconds(1));
      
    3. Verificar si el método Finalize de la Task lanza una excepción:
      GC.Collect();
      GC.WaitForPendingFinalizers();
      
    4. Si el método Finalize lanza una excepción, el proceso debería terminar inmediatamente. Para capturar la excepción y evitar que el proceso se bloquee, puedes suscribirte al evento TaskScheduler.UnobservedTaskException y establecer la bandera e.SetObserved() en true:
      TaskScheduler.UnobservedTaskException += (sender, e) =>
      {
         Console.WriteLine($"Excepción no observada: {e.Exception}");
         e.SetObserved();
      };
      

    Aquí tienes un ejemplo de una aplicación de consola que demuestra los pasos anteriores:

    using System;
    using System.Threading.Tasks;
    
    class Program
    {
        static async Task Main()
        {
            Task.Run(() => throw new Exception("Excepción no observada"));
    
            await Task.Delay(TimeSpan.FromSeconds(1));
    
            GC.Collect();
            GC.WaitForPendingFinalizers();
    
            Console.WriteLine("Proceso completado.");
            Console.ReadKey();
        }
    }
    

    Si el evento TaskScheduler.UnobservedTaskException no está suscrito, al ejecutar el código anterior debería aparecer el siguiente mensaje de error:

    Unhandled exception. System.Exception: Excepción no observada
    

    Si el evento está suscrito y la bandera e.SetObserved() se establece en true, el mensaje de error no debería aparecer y el proceso debería terminar de manera normal.

Comments are closed.