catch eccezione generata in un thread diverso


110

Uno dei miei metodi ( Method1) genera un nuovo thread. Quel thread esegue un metodo ( Method2) e durante l'esecuzione viene generata un'eccezione. Ho bisogno di ottenere le informazioni sull'eccezione sul metodo chiamante ( Method1)

C'è un modo in cui posso catturare questa eccezione in Method1quanto viene lanciata Method2?

Risposte:


182

In .NET 4 e versioni successive, puoi usare Task<T>class invece di creare un nuovo thread. Quindi puoi ottenere eccezioni utilizzando la .Exceptionsproprietà sull'oggetto attività. Ci sono 2 modi per farlo:

  1. In un metodo separato: // elabora l'eccezione nel thread di alcune attività

    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. Nello stesso metodo: // elabora l'eccezione nel thread del chiamante

    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();
        }
    }
    

Nota che l'eccezione che ottieni è AggregateException. Tutte le vere eccezioni sono disponibili tramite ex.InnerExceptionsproprietà.

In .NET 3.5 è possibile utilizzare il codice seguente:

  1. // Elabora l'eccezione nel thread del figlio

    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. Oppure // Elabora l'eccezione nel thread del chiamante

    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();
        }
    }
    

Mi dispiace ma ho dimenticato di dire che sto usando .NET 3.5. Secondo la mia comprensione Task è 4.0 cosa?
Silverlight Student

2
@SilverlightStudent Ok, ho appena aggiornato la mia risposta per soddisfare le tue esigenze.
ossilumina

@oxilumin: grazie e molto apprezzato. Un'altra domanda di follow-up. Se il tuo metodo Test () accetta anche alcuni argomenti, come modificherai il metodo SafeExecute per quegli argomenti?
Silverlight Student

2
@SilverlightStudent In questo caso passerò un lambda invece di Test. Come() => Test(myParameter1, myParameter2)
ossilumina

2
@SilverlightStudent: aggiornato.
ossilumina

9

Non è possibile rilevare l'eccezione in Method1. È tuttavia possibile intercettare l'eccezione in Method2 e registrarla in una variabile che il thread di esecuzione originale può quindi leggere e utilizzare.


Grazie per la risposta. Quindi, se Method1 fa parte di Class1 e ho una variabile di tipo Exception in quella classe. Ogni volta che Method2 genera un'eccezione, imposta anche quella variabile di eccezione in Class1. Sembra un design equo? Esistono metodi di best practice per gestire questo scenario?
Silverlight Student

Corretto, devi solo memorizzare l'eccezione e accedervi in ​​un secondo momento. Non è raro che i metodi vengano eseguiti in futuro (in particolare i callback per quando Method2 è completo) per poi rilanciare quell'eccezione come se fossero stati loro stessi a causarla, ma questo dipende davvero da ciò che si desidera.
ermau

0

Il metodo più semplice per condividere i dati tra thread diversi è shared datail seguente (alcuni sono pseudo codice):

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;
   }
}

Puoi leggere di questo metodo in questa bella introduzione sul multithreading , tuttavia, ho preferito leggerlo in O'Reilly book C# 3.0 in a nutshell, dei fratelli Albahari (2007), che è anche liberamente accessibile su Google Libri, proprio come la versione più recente del libro, perché copre anche il pool di thread, i thread in primo piano e quelli in background, ecc. ecc., con un codice di esempio semplice e carino. (Dichiarazione di non responsabilità: possiedo una copia logora di questo libro)

Nel caso in cui si stia creando un'applicazione WinForms, l'uso dei dati condivisi è particolarmente utile, perché i controlli WinForm non sono thread-safe. Utilizzando un callback per passare i dati dal thread di lavoro a un controllo WinForm, il thread dell'interfaccia utente principale necessita di codice brutto Invoke()per rendere quel controllo thread-safe. Utilizzando invece i dati condivisi e il thread singolo System.Windows.Forms.Timer, con un breve, Intervaldiciamo 0,2 secondi, puoi inviare facilmente informazioni dal thread di lavoro al controllo senza Invoke.


0

Ho avuto un problema particolare in quanto volevo utilizzare elementi, contenenti controlli, da una suite di test di integrazione, quindi devo creare un thread STA. Il codice che ho ottenuto è il seguente, messo qui nel caso in cui altri abbiano lo stesso 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) { ... }

Questa è una copia diretta del codice così com'è. Per altri usi, consiglierei di fornire un'azione o una funzione come parametro e di invocarla sul thread invece di codificare il metodo chiamato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.