Доступ к элементу управления UI из потока BackgroundWorker
У меня есть кнопка в моей форме windows, которая вызывает метод RunWorkerAsync (), это, в свою очередь, выполняет действие, которое затем обновляет список в той же форме.
после завершения события DoWork я назначаю результат для события (которое является списком), я обрабатываю событие RunWorkerCompleted (), а затем выполняю следующий код для обновления моего Listbox
которых вызовы так:
(извиняюсь, код форматирование не будет работать)
теперь, когда я запускаю приложение и нажимаю кнопку обновления, появляется следующее исключение:
Как мне обойти это?
Edit:
исключение выбрасывается в оператор folowing, это происходит в методе DoWork, где я очищаю содержимое, чтобы сохранить список в актуальном состоянии;
listBoxServers.Предметы.Clear();
6 ответов
вот фрагмент, который я нахожу очень удобным:
public static void ThreadSafe(Action action)
{
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal,
new MethodInvoker(action));
}
вы можете передать его любому делегату Action
введите или просто лямбда, как это:
ThreadSafe(() =>
{
[your code here]
});
или
ThreadSafe(listBoxServers.Items.Clear);
вы не можете позвонить Invoke
в списке, но в форме. Для приложений WinForms я использую что-то вроде:
...
this.Invoke((MethodInvoker)delegate()
{
// Do stuff on ANY control on the form.
});
...
в зависимости от версии .NET вам может потребоваться объявить делегат для MethodInvoker
себя
public delegate void MethodInvoker();
однако вы также можете использовать ReportProgress
особенность фонового рабочего. Соответствующий обработчик событий должен вызываться в контексте потока формы.
то, что я сделал, это что-то вроде этого каждый раз, когда вам нужно запустить что-то через потоки:
listBoxServers.BeginInvoke(
(Action)
(() => listBoxServers.Items.Clear()));
фоновые потоки не разрешены для обновления пользовательского интерфейса в приложениях Windows, поэтому вы должны вернуть элемент управления обратно в поток пользовательского интерфейса для фактического обновления.
создайте метод, который вызовет UpdateServerDetails в основном потоке, например:
private void DispatchServerDetails(List<ServerDetails> details)
{
Action<List<ServerDetails>> action = UpdateServerDetails;
Dispatcher.Invoke(action)
}
а потом звоните DispatchServerDetails
вместо UpdateServerDetails
.
некоторые предостережения:
-Это лучше всего работает в приложениях WPF, для WinForms, вам нужно будет прыгать через некоторые обручи, или вы можете использовать InvokeRequired
- Обновление пользовательского интерфейса по-прежнему синхронно, поэтому, если UpdateServerDetails делает много работы, он заблокирует поток пользовательского интерфейса (не ваш случай, просто чтобы быть в безопасности).
использование Invoke в проекте windows forms может быть немного сложным, есть некоторые подводные камни, которые документированы, но легко пропустить. Я рекомендую использовать что-то вроде того, что вы найдете в этом вопросе:
он обрабатывает случаи, когда вызов не требуется, вызывается из разных потоков, дескриптор создается или не создается, etcetcetc. Его можно легко изменить быть SafeInvoke()
и SafeBeginInvoke()
Если вы не поклонник параметра bool.
(включено здесь для вашего удобства:
/// Usage:
this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);
// or
string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);
/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of
/// updater. False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread. If the method is called on the GUI thread,
/// execution is always synchronous.</param>
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
if (uiElement == null)
{
throw new ArgumentNullException("uiElement");
}
if (uiElement.InvokeRequired)
{
if (forceSynchronous)
{
uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
else
{
uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
}
else
{
if (!uiElement.IsHandleCreated)
{
// Do nothing if the handle isn't created already. The user's responsible
// for ensuring that the handle they give us exists.
return;
}
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
Я просто понял более простой способ без использования Invoke:
int fakepercentage = -1;
//some loop here......if no loop exists, just change the value to something else
if (fakepercentage == -1)
{
fakepercentage = -2;
}
else
{
fakepercentage = -1;
}
backgroundworker1.ReportProgress(fakepercentage);
затем в backgroundworker1_ProgressChanged (отправитель объекта, ProgressChangedEventArgs e):
if (e.ProgressPercentage < 0)
{
//access your ui control safely here
}