Как я могу общаться между несколькими потоками? - PullRequest
11 голосов
/ 26 января 2010

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

Чтобы создать плагин через API, который я использую, можно создать собственную команду, наследуя интерфейс IAPICommand. Этот интерфейс включает метод Execute (приложение-приложение). Затем создается экземпляр класса, и программа вызывает метод Execute (), когда пользователь вызывает пользовательскую команду в программе.

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

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

Вот урезанная версия кода.

class MyCommand:IAPICommand
{
    public void Execute(Application app) // method from IAPICommand
    {
        Thread threadTwo= new Thread(ShowFormMethod);
        threadTwo.Start();
    }

    public void ProcessWidget(Widget w, Application app)
    { 
        //uses an App to work some magic on C
        //app must be called from the original thread that called ExecuteCommand()
    }

    //method to open custom form on a seperatethread
    public void ShowFormMethod()
    {
      MyForm form = new MyForm();
      form.ShowDialog();  
    }
}

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

альтернативный текст http://dl.dropbox.com/u/113068/SOMLibThreadingDiagram.jpg

  1. Имеет ли эта диаграмма какой-либо смысл, и если да, то я даже принимаю правильный подход для решения этой проблемы?
  2. Как только основной поток запускает поток пользовательского интерфейса, я хочу, чтобы он дождался, пока пользователь выберет виджеты для обработки, или завершит команду, закрыв форму (красные цифры на диаграмме). Как я могу заставить основной поток ждать, и как я могу заставить его продолжить либо обработку, либо продолжить до конца, когда поток пользовательского интерфейса заканчивается? Я думал, что мог бы ждать основного потока при блокировке монитора. Затем поток пользовательского интерфейса заполнил бы статический список виджетов, которые должны быть обработаны, и затем отправил бы импульс основному потоку для запуска обработки. Поток пользовательского интерфейса также будет отправлять импульс основному потоку, когда форма закрыта, и основной поток будет знать, продолжать ли выполнение команды до конца, если он когда-либо был обработан, когда список обрабатываемых виджетов пуст.
  3. Как разрешить основному потоку сообщать о ходе или завершении обработки виджета обратно в поток пользовательского интерфейса (желтые стрелки на диаграмме)? Я просто использовал метод BeginInvoke () формы для этого?
  4. Как разрешить потоку пользовательского интерфейса отменить обработку виджета (зеленая стрелка на диаграмме)? Я думаю, я мог бы просто установить статический логический флаг, который проверяется перед обработкой каждого виджета?

Ответы [ 4 ]

14 голосов
/ 26 января 2010

Обычно плохая идея иметь несколько потоков в вашем приложении, каждый из которых создает формы. Это не невозможно сделать, но это гораздо сложнее, чем вы думаете, потому что формы, которые находятся в отношениях родитель-потомок, отправляют сообщения друг другу, и когда они это делают, отправляющая сообщение блокирует до тех пор, пока получение обрабатывает это.

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

Если вы соответствуете этому проекту, тогда фоновые потоки могут использовать Control.BeginInvoke для передачи сообщений в поток пользовательского интерфейса без ожидания обработки сообщений.

2 голосов
/ 26 января 2010

В дополнение к другим ответам я рекомендую использовать метод обратного вызова из ProcessWidget для передачи прогресса обратно в вызывающий поток. Чтобы преждевременно остановить рабочий поток, вы можете использовать обратный вызов для возврата сигнала остановки вашему рабочему потоку, если он достаточно часто обновляет вызывающую программу. Или используйте отдельный метод обратного вызова, чтобы периодически проверять ход / нет. Или установите (задыхаясь!) Глобальный статический флаг, который работник периодически проверяет. Или вызовите Thread.Abort в рабочем потоке, чтобы он перехватил исключение ThreadAbortException для очистки любых ресурсов.

1 голос
/ 26 января 2010

Я предполагаю, что хост-приложение является приложением WinForms.

Вам необходимо сохранить SynchronizationContext из исходного потока в вашем методе Execute, а затем вызвать его метод Send для выполнения кода в потоке пользовательского интерфейса хоста.

Например:

class MyCommand:IAPICommand
{
    SynchronzationContext hostContext;
    public void Execute(Application app) // method from IAPICommand
    {
        hostContext = SynchronzationContext.Current;
        Thread threadTwo = new Thread(ShowFormMethod);
        threadTwo.Start();
    }

    public void ProcessWidget(Widget w, Application app)
    { 
        //uses an App to work some magic on C
        //app must be called from the original thread that called ExecuteCommand()
        SomeType someData = null;
        hostContext.Send(delegate { someData = app.SomeMethod(); }, null);
    }
}
0 голосов
/ 09 февраля 2010

Если вы посмотрите на Java swing, это хороший пример того, как это сделать:

1) Главный поток отвечает за обработку всех запросов пользовательского интерфейса. Это удаляет все расы из приложения.

2) Каждый раз, когда нужно выполнить любую «работу», порождайте поток (или пул потоков) и выполняйте работу. Таким образом, основной поток не задерживается, за исключением нескольких микросекунд, а пользовательский интерфейс полностью реагирует на происходящее.

3) Во всех языках должен быть механизм прерывания потока. В Java вы вызываете .interrupt () в потоке, и текущий запущенный поток получает исключение InterruptedException, где бы он ни выполнялся. Ваша задача - поймать это исключение, выяснить, действительно ли вы прерваны (прочитайте javadocs для этой части), и если вы просто позволите себе умереть (вернитесь из метода run).

1 + 2 = ненавязчивое взаимодействие с клиентом

3 = уничтожение потоков

Альтернатива 3 (если 3 слишком сложная) - дать потоку метод .kill (); метод устанавливает флаг уничтожения. Когда вы читаете буфер с жесткого диска в цикле, проверьте, установлен ли флаг уничтожения, не вышел ли он из цикла, закройте обработчики и вернитесь из метода run.

Редактировать: извините, забыл упомянуть отчет о проделанной работе:

Ваша ветка должна иметь публично открытый потокобезопасный метод получения "отчета о прогрессе" или, скорее, структуры данных, содержащей информацию о прогрессе. Ваш поток пользовательского интерфейса должен периодически (скажем, каждые 0,5 секунды) проверять отчет о продвижении потока и обновлять индикатор выполнения пользовательского интерфейса. Под проверкой потока пользовательского интерфейса я подразумеваю, что ваш виджет, показывающий ход выполнения, выполняет запрос на повторную визуализацию с самой последней информацией по таймеру, пока не выполнится.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...