captura exceção que é lançada em um segmento diferente

110

Um dos meus métodos ( Method1) gera um novo thread. Esse thread executa um método ( Method2) e durante a execução uma exceção é lançada. Preciso obter essas informações de exceção no método de chamada ( Method1)

Há alguma maneira de detectar essa exceção Method1que é lançada Method2?

Silverlight Student
fonte

Respostas:

182

No .NET 4 e superior, você pode usar a Task<T>classe em vez de criar um novo thread. Em seguida, você pode obter exceções usando a .Exceptionspropriedade em seu objeto de tarefa. Existem 2 maneiras de fazer isso:

  1. Em um método separado: // Você processa a exceção no thread de alguma tarefa

    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. No mesmo método: // Você processa a exceção no thread do chamador

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

Observe que a exceção que você obtém é AggregateException. Todas as exceções reais estão disponíveis por meio de ex.InnerExceptionspropriedade.

No .NET 3.5, você pode usar o seguinte código:

  1. // Você processa exceção no tópico da criança

    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 // Você processa exceção no thread do chamador

    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();
        }
    }
    
oxilumina
fonte
Desculpe, mas esqueci de mencionar que estou usando o .NET 3.5. Pelo que entendi, a tarefa é 4.0 coisa?
Silverlight Student
2
@SilverlightStudent Ok, acabei de atualizar minha resposta para atender às suas necessidades.
oxilumina
@oxilumin: Obrigado e muito apreciado. Mais uma pergunta de acompanhamento. Se o seu método Test () também aceita alguns argumentos, como você modificará o método SafeExecute para esses argumentos?
Silverlight Student
2
@SilverlightStudent Neste caso, passarei um lambda em vez de Test. Como() => Test(myParameter1, myParameter2)
oxilumina
2
@SilverlightStudent: Atualizado.
oxilumina
9

Você não pode capturar a exceção no Método1. Você pode, no entanto, capturar a exceção no Método2 e gravá-la em uma variável que o encadeamento original de execução pode ler e trabalhar.

Ermau
fonte
Obrigado pela sua resposta. Portanto, If Method1 é parte de Class1 e eu tenho uma variável do tipo Exception nessa classe. Sempre que Method2 lança uma exceção, ele define essa variável de exceção em Class1 também. Parece um design justo? Existe alguma forma de prática recomendada de lidar com esse cenário?
Silverlight Student
Correto, basta armazenar a exceção e acessá-la mais tarde. Não é incomum que métodos executados no futuro (especialmente callbacks para quando o Método2 for concluído) relançar essa exceção como se eles próprios a tivessem causado, mas isso realmente depende do que você deseja.
ermau
0

O método mais simples para compartilhar dados entre diferentes threads é shared datao seguinte (alguns são pseudocódigos):

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

Você pode ler sobre esse método nesta bela introdução sobre multithreading , no entanto, eu preferi ler sobre isso no O'Reilly book C# 3.0 in a nutshell, dos irmãos Albahari (2007), que também pode ser acessado gratuitamente no Google Livros, assim como a versão mais recente do livro, porque também cobre o pool de threads, threads em primeiro plano versus segundo plano, etc etc, com um código de exemplo simples e agradável. (Isenção de responsabilidade: eu possuo uma cópia desgastada deste livro)

No caso de você estar fazendo um aplicativo WinForms, o uso de dados compartilhados é especialmente útil, porque os controles do WinForm não são seguros para threads. Usando um retorno de chamada para passar dados do thread de trabalho de volta para um controle WinForm, o thread de IU principal precisa de um código feio Invoke()para tornar esse controle thread-safe. Usando dados compartilhados, e o thread único System.Windows.Forms.Timer, com um curto Intervalde, digamos, 0,2 segundos, você pode facilmente enviar informações do thread de trabalho para o controle sem Invoke.

Roland
fonte
0

Eu tive um problema específico em que queria usar itens, contendo controles, de um conjunto de testes de integração, então tive que criar um thread STA. O código que obtive é o seguinte, colocado aqui no caso de outros terem o mesmo 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) { ... }

Esta é uma colagem direta do código no estado em que se encontra. Para outros usos, eu recomendaria fornecer uma ação ou função como parâmetro e invocá-la no thread em vez de codificar permanentemente o método chamado.

Richard Petheram
fonte