2016-02-19 7 views
2

Это, вероятно, один из самых частых вопросов в Stackoverflow, однако я не смог найти точный ответ на мой вопрос: Я хотел бы создать шаблон, который позволяет начать поток B из потока A и при определенных условиях (например, когда возникает исключение) вызвать метод в потоке A. В случае исключения правильная нить имеет большое значение, поскольку исключение должно вызовите метод catch в основном потоке A. Если поток A является потоком пользовательского интерфейса, тогда все просто (вызовите .Invoke() или .BeginInvoke() и все). В потоке пользовательского интерфейса есть некоторый механизм, как это делается, и я хотел бы получить представление о том, как можно было бы написать собственный механизм для потока, отличного от UI. Обычно предлагаемым способом для этого является использование перекачки сообщений
http://www.codeproject.com/Articles/32113/Understanding-SynchronizationContext-Part-II
, но цикл while будет блокировать поток A, и это не то, что мне нужно, а не то, как поток потоков пользовательского интерфейса обрабатывает эту проблему. Существует несколько способов обойти эту проблему, но я хотел бы получить более глубокое понимание проблемы и написать свою собственную универсальную утилиту независимо от выбранных методов, например, используя System.Threading.Thread или System.Threading.Tasks.Task или BackgroundWorker или что-нибудь еще и независимо, если есть поток пользовательского интерфейса или нет (например, консольное приложение).

Ниже приведен пример кода, который я пытаюсь использовать для проверки улавливания исключения (который четко указывает на неправильный поток, к которому выбрано исключение). Я буду использовать его как утилиту со всеми функциями блокировки, проверкой работы потока и т. Д., Поэтому я создаю экземпляр класса.метод вызова из другого потока без блокировки потока (или написать собственный SynchronizationContext для потока, отличного от UI). C#

class Program 
{ 
    static void Main(string[] args) 
    { 
     CustomThreads t = new CustomThreads(); 
     try 
     { 
      // finally is called after the first action 
      t.RunCustomTask(ForceException, ThrowException); // Runs the ForceException and in a catch calls the ThrowException 
      // finally is never reached due to the unhandled Exception 
      t.RunCustomThread(ForceException, ThrowException); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.Message); 
     } 
     // well, this is a lie but it is just an indication that thread B was called 
     Console.WriteLine("DONE, press any key"); 
     Console.ReadKey(); 
    } 

    private static void ThrowException(Exception ex) 
    { 
     throw new Exception(ex.Message, ex); 
    } 

    static void ForceException() 
    { 
     throw new Exception("Exception thrown"); 
    } 
} 

public class CustomThreads 
{ 
    public void RunCustomTask(Action action, Action<Exception> action_on_exception) 
    { 
     Task.Factory.StartNew(() => PerformAction(action, action_on_exception)); 
    } 

    public void RunCustomThread(Action action, Action<Exception> action_on_exception) 
    { 
     new Thread(() => PerformAction(action, action_on_exception)).Start(); 
    } 

    private void PerformAction(Action action, Action<Exception> action_on_exception) 
    { 
     try 
     { 
      action(); 
     } 
     catch (Exception ex) 
     { 
      action_on_exception.Invoke(ex); 
     } 
     finally 
     { 
      Console.WriteLine("Finally is called"); 
     } 
    } 
} 

Еще одна интересная особенность, которую я обнаружил, что new Thread() бросает необработанное исключение и finally никогда не вызывается, тогда как new Task() не делает, и finally называется. Может быть, кто-то может прокомментировать причину этой разницы.

+1

Почему бы просто не использовать 'async' и' await' вместо того, чтобы использовать некоторый код pre-TPL с датой 2008 года? Вы понимаете, что 'Invoke' и' BeginInvoke' используют насос сообщений за кулисами? – MickyD

+0

выдержка из https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx 'В рамках обработанного исключения гарантированный запуск завершенного блока. Однако, если исключение необработанно, выполнение блока finally зависит от того, как инициируется операция отмены развязки' – mariovalens

+1

«цикл while блокирует поток A и ... не способ, которым поток нитей обрабатывает эту проблему» - да , да. За кулисами поток пользовательского интерфейса работает в цикле while, так что он может подобрать работу, чтобы сделать - либо события пользовательского интерфейса из окон, либо запросы на запуск кода из других потоков. –

ответ

3

и не так, как UI нить обрабатывает этот вопрос

Это не является точным, это точно как нить UI обрабатывает его. Цикл сообщений является общим решением для producer-consumer problem. Где в типичной программе Windows операционная система, а также другие процессы создают сообщения, и потребляет только один и тот же поток пользовательского интерфейса.

Этот шаблон необходим для того, чтобы иметь дело с кодом, который является принципиально небезопасным. И всегда есть много небезопасного кода, тем более запутанным он становится ниже шансов, что он может быть потокобезопасным. Что-то, что вы можете видеть в .NET, очень мало классов, которые являются поточно-безопасными по дизайну. Что-то простое является списком <> не является потокобезопасным, и вам нужно использовать ключевое слово , чтобы оно было безопасным. Код GUI абсолютно небезопасен, и никакая блокировка не сделает его безопасным.

Не только потому, что трудно определить, куда положить замок заявление, есть куча кода, в котором вы не писали. Подобно обходам сообщений, автоматизации пользовательского интерфейса, программам, которые помещают объекты в буфер обмена, которые вы вставляете, перетаскиваете, расширяете оболочки, которые запускаются при использовании диалогового окна оболочки, такого как OpenFileDialog. Весь этот код является небезопасным, в первую очередь потому, что его автор не имеет, чтобы сделать его потокобезопасным. Если вы отключаете ошибку потока в таком коде, у вас нет номера телефона для вызова и полностью неразрешимой проблемы.

Выполнение вызова метода на thread требуется эта помощь. Невозможно произвольно прервать поток из того, что он делает, и заставить его вызвать метод. Это вызывает ужасные и полностью неразрешимые проблемы с повторным подключением. Как и проблемы, вызванные DoEvents(), но умноженные на тысячу. Когда код входит в цикл диспетчера, он неявно «не работает» и не занят выполнением собственного кода. Таким образом, может потребоваться выполнение запроса из очереди сообщений. Это может по-прежнему идти не так, вы будете снимать свою ногу, когда вы накинете, когда вы находитесь не простоя. Вот почему DoEvents() настолько опасен.

Так что здесь нет ярлыков, вам действительно нужно иметь дело с этим while() loop. То, что это можно сделать, - это то, что у вас есть довольно солидное доказательство, поток пользовательского интерфейса делает это довольно хорошо. Рассмотрим creating your own.