Как убедиться, что пользовательский интерфейс отзывчив, используя BackgroundWorker - PullRequest
5 голосов
/ 18 января 2010

Безопасен ли BackgroundWorker в c # Thread?

Причина, по которой я спрашиваю это, заключается в том, что я получаю

Элементы управления, созданные в одном потоке, не могут быть родителем к контролю на другая нить

исключение с ним. Это мой DoWork код события:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{



    var openFile = document.Open(MyFileName);
    e.Result = openFile;
}

где document - это элемент управления пользовательского интерфейса, который инициализируется при создании родительской формы. При методе Open будут заполнены различные свойства в document.

Я пытался изменить код для вызова, но та же проблема сохраняется. то есть,

document.GetType().GetMethod("Open)".Invoke(document, new object[]{MyFileName})

выдаст ту же ошибку, что и выше.

Есть идеи, как управлять элементом управления document? Другими словами, как заставить работать приведенный выше код?

Редактировать: Было предложено использовать Control.Invoke, но он все равно не работал (оба потока зависли). Это код, который я пробовал:

private delegate bool OpenFile(string filePath);
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{



    OpenFile oF = new OpenFile(document.Open);
    var openFile = Invoke(oF, MyFileName);  // it doesn't really matter whether I use BeginInvoke or Invoke, or other Control.Invoke, the end result is the same. Both the main thread hosting the document and the thread that launches the UI hanged.

    e.Result = openFile;
}

Ответы [ 7 ]

5 голосов
/ 18 января 2010

Проблема не в потоке, а в том, что он пытается вызвать метод в элементе управления пользовательского интерфейса. Как в WPF, так и в WinForms элементы управления могут вызываться только в потоке пользовательского интерфейса (из которых обычно один). Вы не говорите, что используете, но вам нужно вызвать метод Control.Invoke для WinForms или Dispatcher.Invoke для WPF.

Показанный вами метод отражения Invoke() фактически вызовет метод в текущем потоке.

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

Вы можете либо вызвать, как предложил Мехрдад Афшари, либо использовать событие прогресса bgw, которое возвращается в потоке пользовательского интерфейса. Или событие завершения работы, которое также возвращается в потоке пользовательского интерфейса. Разница между ними заключается в том, что WorkCompleted запускается только один раз в конце. Прогресс запущен вами из DoWork.

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

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

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

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

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

Хотя мне неясно, что именно вы подразумеваете под поточной безопасностью BackgroundWorker, проблема не в этом объекте;Элементы управления Windows Forms предназначены для управления одним потоком (потоком пользовательского интерфейса).Вы не должны манипулировать объектами Windows Forms в разных потоках.Вы можете вызывать действия в потоке пользовательского интерфейса из других потоков, используя метод Control.Invoke (метод Invoke, который вы используете в настоящее время, предоставляется отражением и совершенно не связан с этой проблемой):

Invoke(new Action(MethodToRunInUIThread));

void MethodToRunInUIThread() {
    // do stuff here.
}

Кстати, не имеет смысла использовать фонового работника, если все, что вы делаете - это манипулируете объектами пользовательского интерфейса.

0 голосов
/ 19 января 2010

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

Control.Invoke () выполнит делегат также в другом потоке, но заставит вызывающий поток ждать его завершения.

Обычно в Windows Forms лучше по возможности использовать Control.BeginInvoke (), чтобы избежать взаимной блокировки между потоками, которая может возникнуть, когда один поток ожидает другого, как в Control.Invoke ().

Если объект «документ» наследуется от System.Windows.Forms.Control, вы можете просто вызвать document.BeginInvoke (myDelegate).

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

Похоже, вы запутались в различных типах Invoke / BeginInvoke (понятно). Этот предыдущий вопрос: В чем разница между Invoke и BeginInvoke? и ответ Джона Скитса должен помочь прояснить ситуацию.

0 голосов
/ 18 января 2010

@ Гравитон, найдено соответствующее задание с ответом здесь .Человек использовал BackgroundWorker для обновления текстового поля, применяется та же концепция (у вас только один рабочий поток).

0 голосов
/ 18 января 2010

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

...