Подтвердить что ты не робот

Как мне взаимодействовать между несколькими потоками?

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

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

Ответ 1

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

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

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

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

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

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

3 = убивающие потоки

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

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

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