catch exception qui est levée dans un thread différent


110

Une de mes méthodes ( Method1) génère un nouveau thread. Ce thread exécute une méthode ( Method2) et pendant l'exécution, une exception est levée. J'ai besoin d'obtenir ces informations d'exception sur la méthode d'appel ( Method1)

Y a-t-il une manière dont je peux attraper cette exception en Method1ce qu'elle est lancée Method2?

Réponses:


182

Dans .NET 4 et supérieur, vous pouvez utiliser la Task<T>classe au lieu de créer un nouveau thread. Ensuite, vous pouvez obtenir des exceptions en utilisant la .Exceptionspropriété sur votre objet de tâche. Il y a 2 façons de le faire:

  1. Dans une méthode distincte: // Vous traitez l'exception dans le thread de certaines tâches

    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. Dans la même méthode: // Vous traitez l'exception dans le thread de l' appelant

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

Notez que l'exception que vous obtenez est AggregateException. Toutes les exceptions réelles sont disponibles via la ex.InnerExceptionspropriété.

Dans .NET 3.5, vous pouvez utiliser le code suivant:

  1. // Vous traitez l'exception dans le thread de l' enfant

    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. Ou // Vous traitez l'exception dans le thread de l' appelant

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

Désolé mais j'ai oublié de mentionner que j'utilise .NET 3.5. Selon ma compréhension, la tâche est une chose 4.0?
Silverlight Student

2
@SilverlightStudent Ok, je viens de mettre à jour ma réponse pour répondre à vos exigences.
oxilumin

@oxilumin: Merci et beaucoup apprécié. Encore une question complémentaire. Si votre méthode Test () prend également quelques arguments, comment allez-vous modifier la méthode SafeExecute pour ces arguments?
Étudiant Silverlight

2
@SilverlightStudent Dans ce cas, je passerai un lambda au lieu de Test. J'aime() => Test(myParameter1, myParameter2)
oxilumin

2
@SilverlightStudent: mis à jour.
oxilumin

9

Vous ne pouvez pas intercepter l'exception dans Method1. Vous pouvez, cependant, intercepter l'exception dans Method2 et l'enregistrer dans une variable que le thread d'exécution d'origine peut ensuite lire et utiliser.


Merci pour votre réponse. Donc, si Method1 fait partie de Class1 et que j'ai une variable de type Exception dans cette classe. Chaque fois que Method2 lève une exception, elle définit également cette variable d'exception dans Class1. Cela ressemble-t-il à un design juste? Existe-t-il des meilleures pratiques pour gérer ce scénario?
Silverlight Student

Correct, il vous suffit de stocker l'exception et d'y accéder plus tard. Il n'est pas rare que les méthodes exécutées à l'avenir (en particulier les rappels lorsque la méthode 2 est terminée) rejettent ensuite cette exception comme si elles l'avaient elles-mêmes provoquée, mais cela dépend vraiment de ce que vous voulez.
ermau

0

La méthode la plus simple pour partager des données entre différents threads est shared datala suivante (certaines sont du pseudo-code):

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

Vous pouvez lire à propos de cette méthode dans cette belle introduction sur le multithreading , cependant, j'ai préféré lire à ce sujet dans le O'Reilly book C# 3.0 in a nutshell, par les frères Albahari (2007), qui est également librement accessible sur Google Books, tout comme la nouvelle version du livre, car il couvre également le pool de threads, les threads de premier plan par rapport aux threads d'arrière-plan, etc., avec un exemple de code simple et agréable. (Avertissement: je possède un exemplaire usé de ce livre)

Dans le cas où vous créez une application WinForms, l'utilisation de données partagées est particulièrement pratique, car les contrôles WinForm ne sont pas thread-safe. En utilisant un rappel pour transmettre les données du thread de travail à un contrôle WinForm, le thread d'interface utilisateur principal a besoin d'un code laid Invoke()pour rendre ce contrôle thread-safe. En utilisant des données partagées à la place, et le thread unique System.Windows.Forms.Timer, avec un court Intervalde par exemple 0,2 seconde, vous pouvez facilement envoyer des informations du thread de travail au contrôle sans Invoke.


0

J'ai eu un problème particulier en ce sens que je voulais utiliser des éléments, contenant des contrôles, à partir d'une suite de tests d'intégration, donc je dois créer un thread STA. Le code avec lequel j'ai abouti est le suivant, mis ici au cas où d'autres auraient le même problème.

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

Il s'agit d'un collage direct du code tel quel. Pour d'autres utilisations, je recommanderais de fournir une action ou une fonction en tant que paramètre et de l'invoquer sur le thread au lieu de coder en dur la méthode appelée.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.