captura de excepción que se lanza en un hilo diferente

109

Uno de mis métodos ( Method1) genera un nuevo hilo. Ese hilo ejecuta un método ( Method2) y durante la ejecución se lanza una excepción. Necesito obtener esa información de excepción en el método de llamada ( Method1)

¿Hay alguna forma en que pueda detectar esta excepción en la Method1que se incluye Method2?

Estudiante de Silverlight
fuente

Respuestas:

181

En .NET 4 y superior, puede usar la Task<T>clase en lugar de crear un nuevo hilo. Entonces puede obtener excepciones usando la .Exceptionspropiedad en su objeto de tarea. Hay 2 formas de hacerlo:

  1. En un método separado: // Procesas la excepción en el hilo de alguna tarea

    class Program
    {
        static void Main(string[] args)
        {
            Task<int> task = new Task<int>(Test);
            task.ContinueWith(ExceptionHandler, TaskContinuationOptions.OnlyOnFaulted);
            task.Start();
            Console.ReadLine();
        }
    
        static int Test()
        {
            throw new Exception();
        }
    
        static void ExceptionHandler(Task<int> task)
        {
            var exception = task.Exception;
            Console.WriteLine(exception);
        }
    }
    
  2. En el mismo método: // Procesas la excepción en el hilo de la persona que llama

    class Program
    {
        static void Main(string[] args)
        {
            Task<int> task = new Task<int>(Test);
            task.Start();
    
            try
            {
                task.Wait();
            }
            catch (AggregateException ex)
            {
                Console.WriteLine(ex);    
            }
    
            Console.ReadLine();
        }
    
        static int Test()
        {
            throw new Exception();
        }
    }
    

Tenga en cuenta que la excepción que obtiene es AggregateException. Todas las excepciones reales están disponibles a través de la ex.InnerExceptionspropiedad.

En .NET 3.5 puede utilizar el siguiente código:

  1. // Procesas la excepción en el hilo del niño

    class Program
    {
        static void Main(string[] args)
        {
            Exception exception = null;
            Thread thread = new Thread(() => SafeExecute(() => Test(0, 0), Handler));
            thread.Start();            
    
            Console.ReadLine();
        }
    
        private static void Handler(Exception exception)
        {        
            Console.WriteLine(exception);
        }
    
        private static void SafeExecute(Action test, Action<Exception> handler)
        {
            try
            {
                test.Invoke();
            }
            catch (Exception ex)
            {
                Handler(ex);
            }
        }
    
        static void Test(int a, int b)
        {
            throw new Exception();
        }
    }
    
  2. O // procesas la excepción en el hilo de la persona que llama

    class Program
    {
        static void Main(string[] args)
        {
            Exception exception = null;
            Thread thread = new Thread(() => SafeExecute(() => Test(0, 0), out exception));
    
            thread.Start();            
    
            thread.Join();
    
            Console.WriteLine(exception);    
    
            Console.ReadLine();
        }
    
        private static void SafeExecute(Action test, out Exception exception)
        {
            exception = null;
    
            try
            {
                test.Invoke();
            }
            catch (Exception ex)
            {
                exception = ex;
            }
        }
    
        static void Test(int a, int b)
        {
            throw new Exception();
        }
    }
    
oxiluminio
fuente
Lo siento, pero olvidé mencionar que estoy usando .NET 3.5. Según mi entendimiento, ¿la tarea es 4.0?
Estudiante de Silverlight
2
@SilverlightStudent Ok, acabo de actualizar mi respuesta para satisfacer sus requisitos.
oxilumin
@oxilumin: Gracias y muy apreciado. Una pregunta de seguimiento más. Si su método Test () también toma algunos argumentos, ¿cómo modificará el método SafeExecute para esos argumentos?
Estudiante de Silverlight
2
@SilverlightStudent En este caso, pasaré una lambda en lugar de Test. Me gusta() => Test(myParameter1, myParameter2)
oxilumin
2
@SilverlightStudent: actualizado.
oxilumin
9

No puede detectar la excepción en Method1. Sin embargo, puede capturar la excepción en Method2 y registrarla en una variable que el hilo de ejecución original pueda leer y trabajar con ella.

ermau
fuente
Gracias por su respuesta. Entonces, si Method1 es parte de Class1 y tengo una variable de tipo Exception en esa clase. Siempre que Method2 lanza una excepción, también establece esa variable de excepción en Class1. ¿Suena como un diseño justo? ¿Existen formas de mejores prácticas para manejar este escenario?
Estudiante de Silverlight
Correcto, simplemente almacena la excepción y accede a ella más tarde. No es raro que los métodos que se ejecuten en el futuro (especialmente las devoluciones de llamada para cuando el Método2 esté completo) luego vuelvan a generar esa excepción como si ellos mismos la hubieran causado, pero esto realmente depende de lo que desee.
ermau
0

El método más simple para compartir datos entre diferentes hilos es el shared datasiguiente (algunos son pseudocódigo):

class MyThread
{
   public string SharedData;

   public void Worker()
   {
      ...lengthy action, infinite loop, etc...
      SharedData = "whatever";
      ...lengthy action...
      return;
   }
}

class Program
{
   static void Main()
   {
      MyThread m = new MyThread();
      Thread WorkerThread = new Thread(m.Worker);
      WorkerThread.Start();

      loop//or e.g. a Timer thread
      {
         f(m.SharedData);
      }
      return;
   }
}

Puedes leer sobre este método en esta bonita introducción sobre el multihilo , sin embargo, preferí leer sobre esto en el O'Reilly book C# 3.0 in a nutshell, de los hermanos Albahari (2007), que también es de libre acceso en Google Books, al igual que la versión más reciente del libro, porque también cubre la agrupación de subprocesos, subprocesos en primer plano frente a subprocesos en segundo plano, etc., con un código de ejemplo agradable y simple. (Descargo de responsabilidad: tengo una copia gastada de este libro)

En caso de que esté creando una aplicación WinForms, el uso de datos compartidos es especialmente útil, porque los controles de WinForm no son seguros para subprocesos. Al usar una devolución de llamada para pasar datos del hilo de trabajo a un control de WinForm, el hilo de la interfaz de usuario principal necesita un código desagradable Invoke()para que ese control sea seguro para los hilos. En su lugar, utilizando datos compartidos y el subproceso único System.Windows.Forms.Timer, con menos Intervalde 0,2 segundos, puede enviar fácilmente información desde el subproceso de trabajo al control sin él Invoke.

Roland
fuente
0

Tuve un problema particular porque quería usar elementos, que contenían controles, de un conjunto de pruebas de integración, así que tengo que crear un hilo STA. El código con el que terminé es el siguiente, puesto aquí en caso de que otros tengan el mismo problema.

    public Boolean? Dance(String name) {

        // Already on an STA thread, so just go for it
        if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) return DanceSTA(name);

        // Local variable to hold the caught exception until the caller can rethrow
        Exception lException = null;

        Boolean? lResult = null;

        // A gate to hold the calling thread until the called thread is done
        var lGate = new ManualResetEvent(false);

        var lThreadStart = new ThreadStart(() => {
            try {
                lResult = DanceSTA(name);
            } catch (Exception ex) {
                lException = ex;
            }
            lGate.Set();
        });

        var lThread = new Thread(lThreadStart);
        lThread.SetApartmentState(ApartmentState.STA);
        lThread.Start();

        lGate.WaitOne();

        if (lException != null) throw lException;

        return lResult;
    }

    public Boolean? DanceSTA(String name) { ... }

Se trata de un pegado directo del código tal cual. Para otros usos, recomendaría proporcionar una acción o función como parámetro e invocarla en el hilo en lugar de codificar el método llamado.

Richard Petheram
fuente