исключение catch, которое создается в другом потоке

110

Один из моих методов ( Method1) порождает новый поток. Этот поток выполняет метод ( Method2), и во время выполнения возникает исключение. Мне нужно получить эту информацию об исключении в вызывающем методе ( Method1)

Есть ли каким - то образом я могу поймать это исключение в Method1том , что брошен в Method2?

Студент Silverlight
источник

Ответы:

182

В .NET 4 и выше вы можете использовать Task<T>класс вместо создания нового потока. Затем вы можете получить исключения, используя .Exceptionsсвойство объекта задачи. Сделать это можно двумя способами:

  1. Отдельным методом: // Вы обрабатываете исключение в каком- то потоке задачи

    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. Тем же методом: // Вы обрабатываете исключение в потоке вызывающей стороны

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

Обратите внимание, что вы получаете исключение AggregateException. Все реальные исключения доступны через ex.InnerExceptionsсобственность.

В .NET 3.5 вы можете использовать следующий код:

  1. // Вы обрабатываете исключение в дочернем потоке

    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. Или // Вы обрабатываете исключение в потоке вызывающей стороны

    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();
        }
    }
оксилумин
источник
Извините, но я забыл упомянуть, что использую .NET 3.5. Насколько я понимаю, задача 4.0?
Студент Silverlight,
2
@SilverlightStudent Хорошо, я только что обновил свой ответ, чтобы удовлетворить ваши требования.
оксилумин
@oxilumin: Спасибо и очень признателен. Еще один уточняющий вопрос. Если ваш метод Test () также принимает несколько аргументов, как вы измените метод SafeExecute для этих аргументов?
Студент Silverlight
2
@SilverlightStudent В этом случае я передам лямбду вместо Test. Like() => Test(myParameter1, myParameter2)
оксилумин
2
@SilverlightStudent: Обновлено.
оксилумин
9

Вы не можете поймать исключение в Method1. Однако вы можете перехватить исключение в Method2 и записать его в переменную, которую исходный поток выполнения может затем прочитать и с которой будет работать.

Ermau
источник
Спасибо за ваш ответ. Итак, если Method1 является частью Class1, и у меня есть переменная типа Exception в этом классе. Всякий раз, когда Method2 выдает исключение, он также устанавливает эту переменную исключения в Class1. Звучит как честный дизайн? Есть ли какие-либо передовые способы решения этого сценария?
Студент Silverlight
Правильно, вы просто сохраняете исключение и получаете к нему доступ позже. Нередко методы, запускаемые в будущем (особенно обратные вызовы, когда метод Method2 завершен), затем повторно генерируют это исключение, как если бы они сами его вызвали, но это действительно зависит от того, что вы хотите.
ermau
0

Самый простой способ обмена данными между разными потоками выглядит shared dataследующим образом (некоторые из них являются псевдокодом):

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'Reilly book C# 3.0 in a nutshellкниге братьев Альбахари (2007), которая также находится в свободном доступе в Google Книгах, как и более новая версия книги. потому что он также охватывает пул потоков, потоки переднего плана по сравнению с фоновыми потоками и т. д. с красивым и простым примером кода. (Отказ от ответственности: у меня есть изношенная копия этой книги)

Если вы создаете приложение WinForms, использование общих данных особенно удобно, поскольку элементы управления WinForm не являются потокобезопасными. Используя обратный вызов для передачи данных из рабочего потока обратно в элемент управления WinForm, основной поток пользовательского интерфейса нуждается в уродливом коде, Invoke()чтобы сделать этот элемент управления потокобезопасным. Используя вместо этого общие данные и однопоточные System.Windows.Forms.Timer, с коротким, Intervalскажем, 0,2 секунды, вы можете легко отправлять информацию из рабочего потока в элемент управления без Invoke.

Роланд
источник
0

У меня была особая проблема в том, что я хотел использовать элементы, содержащие элементы управления, из набора интеграционных тестов, поэтому мне пришлось создать поток STA. Код, который у меня получился, выглядит следующим образом, на случай, если у других возникнет такая же проблема.

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

Это прямая вставка кода как есть. Для других целей я бы рекомендовал предоставить действие или функцию в качестве параметра и вызывать их в потоке вместо жесткого кодирования вызываемого метода.

Ричард Петерам
источник