Fang eine Ausnahme, die in einem anderen Thread ausgelöst wird

109

Eine meiner Methoden ( Method1) erzeugt einen neuen Thread. Dieser Thread führt eine Methode ( Method2) aus und während der Ausführung wird eine Ausnahme ausgelöst. Ich muss diese Ausnahmeinformationen über die aufrufende Methode ( Method1) erhalten

Gibt es eine Möglichkeit, diese Ausnahme zu erkennen Method1, die eingeworfen wird Method2?

Silverlight Student
quelle

Antworten:

181

In .NET 4 und höher können Sie die Task<T>Klasse verwenden, anstatt einen neuen Thread zu erstellen. Anschließend können Sie mithilfe der .ExceptionsEigenschaft für Ihr Aufgabenobjekt Ausnahmen abrufen. Es gibt zwei Möglichkeiten, dies zu tun:

  1. In einer separaten Methode: // Sie verarbeiten Ausnahmen im Thread einer Aufgabe

    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. In derselben Methode: // Sie verarbeiten eine Ausnahme im Thread des Aufrufers

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

Beachten Sie, dass die Ausnahme, die Sie erhalten, ist AggregateException. Alle realen Ausnahmen sind durch ex.InnerExceptionsEigentum verfügbar .

In .NET 3.5 können Sie den folgenden Code verwenden:

  1. // Sie verarbeiten eine Ausnahme im Thread des Kindes

    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. Oder // Sie verarbeiten eine Ausnahme im Thread des Aufrufers

    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();
        }
    }
    
Oxilumin
quelle
Entschuldigung, aber ich habe vergessen zu erwähnen, dass ich .NET 3.5 verwende. Nach meinem Verständnis ist Aufgabe 4.0 Sache?
Silverlight Student
2
@SilverlightStudent Ok, ich habe gerade meine Antwort aktualisiert, um Ihre Anforderungen zu erfüllen.
Oxilumin
@ Oxilumin: Danke und sehr geschätzt. Noch eine Folgefrage. Wenn Ihre Test () -Methode auch einige Argumente akzeptiert, wie können Sie dann die SafeExecute-Methode für diese Argumente ändern?
Silverlight Student
2
@ SilverlightStudent In diesem Fall übergebe ich stattdessen ein Lambda Test. Like() => Test(myParameter1, myParameter2)
Oxilumin
2
@ SilverlightStudent: Aktualisiert.
Oxilumin
9

Sie können die Ausnahme in Methode1 nicht abfangen. Sie können die Ausnahme jedoch in Methode 2 abfangen und in einer Variablen aufzeichnen, die der ursprüngliche Ausführungsthread dann lesen und bearbeiten kann.

ermau
quelle
Vielen Dank für Ihre Antwort. Wenn also Methode1 Teil von Klasse1 ist und ich in dieser Klasse eine Variable vom Typ Exception habe. Immer wenn Methode2 eine Ausnahme auslöst, wird diese Ausnahmevariable auch in Klasse1 festgelegt. Klingt es nach einem fairen Design? Gibt es Best-Practice-Methoden für den Umgang mit diesem Szenario?
Silverlight Student
Richtig, Sie speichern nur die Ausnahme und greifen später darauf zu. Es ist nicht ungewöhnlich, dass in Zukunft ausgeführte Methoden (insbesondere Rückrufe, wenn Methode2 abgeschlossen ist) diese Ausnahme erneut auslösen, als hätten sie sie selbst verursacht. Dies hängt jedoch wirklich davon ab, was Sie möchten.
Ermau
0

Die einfachste Methode zum Teilen von Daten zwischen verschiedenen Threads ist shared datadie folgende (einige sind Pseudocode):

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

Sie können über diese Methode in dieser schönen Einführung zum Thema Multithreading lesen. Ich habe es jedoch vorgezogen, darüber in der O'Reilly book C# 3.0 in a nutshellvon den Brüdern Albahari (2007) zu lesen , die ebenso wie die neuere Version des Buches auch in Google Books frei zugänglich ist. weil es auch Thread-Pooling, Vordergrund- und Hintergrund-Threads usw. usw. mit nettem und einfachem Beispielcode abdeckt. (Haftungsausschluss: Ich besitze eine abgenutzte Ausgabe dieses Buches)

Wenn Sie eine WinForms-Anwendung erstellen, ist die Verwendung freigegebener Daten besonders praktisch, da die WinForm-Steuerelemente nicht threadsicher sind. Wenn Sie einen Rückruf verwenden, um Daten vom Worker-Thread an ein WinForm-Steuerelement zurückzugeben, benötigt der Haupt-UI-Thread hässlichen Code Invoke(), um dieses Steuerelement threadsicher zu machen. Wenn Sie stattdessen gemeinsam genutzte Daten und den Single-Thread verwenden System.Windows.Forms.Timer, können Sie mit einer kurzen Zeit Intervalvon beispielsweise 0,2 Sekunden problemlos Informationen vom Worker-Thread an das Steuerelement senden, ohne Invoke.

Roland
quelle
0

Ich hatte ein besonderes Problem darin, dass ich Elemente mit Steuerelementen aus einer Integrationstestsuite verwenden wollte, also einen STA-Thread erstellen musste. Der Code, mit dem ich am Ende endete, lautet wie folgt, falls andere das gleiche Problem haben.

    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) { ... }

Dies ist eine direkte Einfügung des Codes wie er ist. Für andere Zwecke würde ich empfehlen, eine Aktion oder Funktion als Parameter anzugeben und diese im Thread aufzurufen, anstatt die aufgerufene Methode fest zu codieren.

Richard Petheram
quelle