Доступ к элементу управления UI из потока BackgroundWorker

У меня есть кнопка в моей форме windows, которая вызывает метод RunWorkerAsync (), это, в свою очередь, выполняет действие, которое затем обновляет список в той же форме.

после завершения события DoWork я назначаю результат для события (которое является списком), я обрабатываю событие RunWorkerCompleted (), а затем выполняю следующий код для обновления моего Listbox

alt text

которых вызовы так:

alt text

(извиняюсь, код форматирование не будет работать)

теперь, когда я запускаю приложение и нажимаю кнопку обновления, появляется следующее исключение:

alt text

Как мне обойти это?

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 может быть немного сложным, есть некоторые подводные камни, которые документированы, но легко пропустить. Я рекомендую использовать что-то вроде того, что вы найдете в этом вопросе:

целесообразно ли расширить контроль, чтобы обеспечить последовательно безопасную функциональность Invoke/BeginInvoke?

он обрабатывает случаи, когда вызов не требуется, вызывается из разных потоков, дескриптор создается или не создается, 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
}