вилучення вилучення, яке кидається в різні нитки


110

Один із моїх методів ( Method1) породить нову нитку. Цей потік виконує метод ( Method2) і під час виконання викиду викидається виняток. Мені потрібно отримати цю інформацію про виключення щодо методу виклику ( Method1)

Чи є якимось чином я можу зловити цей виняток у Method1тому, що кинуто Method2?

Відповіді:


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. Начебто() => Test(myParameter1, myParameter2)
оксилюмін

2
@SilverlightStudent: оновлено.
оксилін

9

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


Дякую за Вашу відповідь. Отже, якщо Method1 є частиною Class1 і у мене є змінна типу Exception у цьому класі. Щоразу, коли Method2 видає виняток, він також встановлює цю змінну винятків у Class1. Це звучить як чесний дизайн? Чи є найкращі способи вживання цього сценарію?
Студент Silverlight

Правильно, ви просто зберігаєте виняток і отримуєте доступ до нього пізніше. Це не рідкість, коли методи, запущені в майбутньому (особливо зворотні виклики, коли метод2 завершений), потім повторно скидають цей виняток так, ніби вони самі його спричинили, але це дійсно залежить від того, що ви хочете.
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) { ... }

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.