2010-01-26 6 views
12

Я пишу плагин для другой программы, которая использует родную программу, чтобы открыть серию файлов для извлечения некоторых данных. Одна из проблем, с которой я столкнулась, - это процесс, который занимает много времени, и я хочу, чтобы пользовательский интерфейс не висел. Кроме того, я также хочу дать пользователю возможность отменить процесс до его завершения. Раньше я использовал фонового работника для этого типа вещей, но в этом случае я не думаю, что 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(); 
    } 
} 

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

alt text http://dl.dropbox.com/u/113068/SOMLibThreadingDiagram.jpg

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

Как отметил Джон, ваша жизнь будет намного проще, если вы будете использовать все свои материалы в основном потоке пользовательского интерфейса и делегируете тяжелую обработку фоновым потокам. В этом сценарии общение легко осуществляется с помощью методов Control.Invoke – mfeingold

+0

Как можно отменить обработку, если поток, который вы должны использовать для переадресации в приложение, занят обработкой и недоступен для вас? Можете ли вы отменить второй поток, но не обрабатывать второй поток? –

+0

Да, пользователь запросит отмену во втором (UI) потоке, чтобы отменить обработку в основном потоке приложения. –

ответ

1

Я предполагаю, что приложение-хост является приложением 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

А как хорошо, но как получается форма для «ProcessWidget()» или «HostContext»? Разве мне не нужно каким-то образом ссылаться на один из них, когда я создаю форму в другом потоке? –

+0

Да. Вы можете передать экземпляр класса 'MyCommand' в форму. – SLaks

+0

Хорошо, я начинаю понимать это. Как получить исходный поток, чтобы подождать второй поток? В действительности Execute() возвращает перечисление, которое передает сообщения, если команда выполнена успешно. Как получить основной поток Execute() для ожидания запроса из FormThread и затем перенаправить себя к методу ProcessWidget()? Я не хочу, чтобы поток Execute() возвращался к основной программе до тех пор, пока пользователь не закончил с нитью формы. –

15

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

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

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

+0

Я понимаю, что вы говорите, но поскольку моя программа является подключаемым модулем для другой программы, на самом деле я не знаю, какой поток выполняет «обработка». Это должно быть сделано в потоке подключаемого модуля, поэтому, чтобы отделить пользовательский интерфейс от обработки, пользовательский интерфейс должен идти во вторичном потоке. –

+0

@eric: высасывает вас тогда. Control.BeginInvoke по-прежнему выводит вас из вашего фонового потока в ваш поток пользовательского интерфейса без ожидания, тем больше вы можете избежать того, что фоновый поток ожидает потока пользовательского интерфейса, тем лучше. Невозможно затормозить, если ни одна из ваших нитей не будет ждать. –

+0

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

2

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

0

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

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

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

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

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

3 = убивающие нити

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

Edit: извините забыл упомянуть доклад:

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

Смежные вопросы