catch wyjątek, który jest zgłaszany w innym wątku

110

Jedna z moich metod ( Method1) tworzy nowy wątek. Ten wątek wykonuje metodę ( Method2) i podczas egzekwowania generowany jest wyjątek. Potrzebuję informacji o wyjątku w metodzie wywołującej ( Method1)

Czy jest jakiś sposób, w jaki mogę złapać ten wyjątek, Method1który jest wrzucany Method2?

Student Silverlight
źródło

Odpowiedzi:

182

W .NET 4 i nowszych możesz użyć Task<T>class zamiast tworzyć nowy wątek. Następnie możesz uzyskać wyjątki za pomocą .Exceptionswłaściwości obiektu zadania. Można to zrobić na dwa sposoby:

  1. W osobnej metodzie: // przetwarzasz wyjątek w wątku jakiegoś zadania

    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. W ten sam sposób: // przetwarzasz wyjątek w wątku wywołującego

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

Zauważ, że wyjątek, który otrzymujesz, to AggregateException. Wszystkie rzeczywiste wyjątki są dostępne poprzez ex.InnerExceptionswłasność.

W .NET 3.5 możesz użyć następującego kodu:

  1. // Przetwarzasz wyjątek w wątku dziecka

    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. Lub // Przetwarzasz wyjątek w wątku wywołującego

    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();
        }
    }
oksylumin
źródło
Przepraszam, ale zapomniałem wspomnieć, że używam .NET 3.5. Jak rozumiem, zadanie to 4.0?
Silverlight Student
2
@SilverlightStudent Ok, właśnie zaktualizowałem moją odpowiedź, aby spełnić Twoje wymagania.
oxilumin
@oxilumin: Dziękuję i bardzo doceniam. Jeszcze jedno pytanie uzupełniające. Jeśli Twoja metoda Test () również przyjmuje kilka argumentów, to w jaki sposób zmodyfikujesz metodę SafeExecute dla tych argumentów?
Silverlight Student
2
@SilverlightStudent W tym przypadku podam zamiast niego lambdę Test. Podobnie jak() => Test(myParameter1, myParameter2)
oxilumin
2
@SilverlightStudent: Zaktualizowano.
oxilumin
9

Nie możesz złapać wyjątku w Method1. Możesz jednak złapać wyjątek w Method2 i zapisać go w zmiennej, którą oryginalny wątek wykonania może następnie odczytać i pracować z nim.

ermau
źródło
Dzięki za twoją odpowiedź. Więc jeśli Method1 jest częścią Class1 i mam zmienną typu Exception w tej klasie. Za każdym razem, gdy Method2 zgłasza wyjątek, ustawia również tę zmienną wyjątku w Class1. Czy to brzmi jak uczciwy projekt? Czy istnieją najlepsze sposoby postępowania w tym scenariuszu?
Silverlight Student
Prawidłowo, po prostu zapisujesz wyjątek i uzyskujesz do niego dostęp później. Często zdarza się, że metody uruchomione w przyszłości (zwłaszcza wywołania zwrotne, gdy Metoda 2 jest kompletna), ponownie generują wyjątek tak, jakby to one same spowodowały, ale to naprawdę zależy od tego, czego chcesz.
ermau
0

Najprostsza metoda udostępniania danych między różnymi wątkami jest shared datanastępująca (niektóre to pseudokod):

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

O tej metodzie możesz przeczytać w tym miłym wprowadzeniu o wielowątkowości , jednak ja wolałem przeczytać o tym w O'Reilly book C# 3.0 in a nutshellopracowaniu braci Albahari (2007), które jest również dostępne bezpłatnie w Google Books, podobnie jak nowsza wersja książki, ponieważ obejmuje również łączenie wątków, wątki pierwszego planu i wątki w tle itp., Z ładnym i prostym przykładowym kodem. (Zastrzeżenie: mam zniszczoną kopię tej książki)

Jeśli tworzysz aplikację WinForms, korzystanie z udostępnionych danych jest szczególnie przydatne, ponieważ kontrolki WinForm nie są bezpieczne wątkowo. Używając wywołania zwrotnego do przekazywania danych z wątku roboczego z powrotem do kontrolki WinForm, główny wątek interfejsu użytkownika wymaga brzydkiego kodu, Invoke()aby zapewnić bezpieczeństwo wątku kontrolnego. Zamiast tego, korzystając z udostępnionych danych i jednowątkowego System.Windows.Forms.Timer, w krótkim czasie, Intervalpowiedzmy 0,2 sekundy, możesz łatwo wysyłać informacje z wątku roboczego do kontrolki bez Invoke.

Roland
źródło
0

Miałem szczególny problem polegający na tym, że chciałem użyć elementów zawierających kontrolki z zestawu testów integracji, więc musiałem utworzyć wątek STA. Kod, który otrzymałem, jest następujący, wstaw go tutaj na wypadek, gdyby inni mieli ten sam problem.

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

To jest bezpośrednie wklejenie kodu w obecnej postaci. W przypadku innych zastosowań zalecałbym podanie akcji lub funkcji jako parametru i wywołanie jej w wątku zamiast kodowania wywoływanej metody na stałe.

Richard Petheram
źródło