Доступ к элементу управления пользовательского интерфейса из потока BackgroundWorker - PullRequest
9 голосов
/ 13 декабря 2010

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

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

alt text

, который вызывает это:

alt text

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

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

alt text

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

Редактировать:

Исключение выдается в следующем предложении, это происходит в методе DoWork, где я очищаю содержимоеобновлять список;

listBoxServers.Items.Clear ();

Ответы [ 6 ]

13 голосов
/ 13 декабря 2010

Вы не можете звонить Invoke в списке, но в форме. Для приложений WinForms я использую что-то вроде:

...
this.Invoke((MethodInvoker)delegate()
{
    // Do stuff on ANY control on the form.
});
...

В зависимости от версии .NET может потребоваться объявить делегата для MethodInvoker как

.
public delegate void MethodInvoker();

Однако вы можете также рассмотреть возможность использования ReportProgress в Background Worker. Соответствующий обработчик события должен вызываться в контексте потока формы.

11 голосов
/ 13 декабря 2010

Вот фрагмент кода, который мне очень пригодится:

public static void ThreadSafe(Action action)
{
    Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, 
        new MethodInvoker(action));
}

Вы можете передать его любому делегату типа Action или просто лямбда-выражению, например так:

ThreadSafe(() =>
{
    [your code here]
});

или

ThreadSafe(listBoxServers.Items.Clear);
9 голосов
/ 13 декабря 2010

То, что я делал, происходит примерно так каждый раз, когда вам нужно запустить что-то между потоками:

listBoxServers.BeginInvoke(
    (Action)
    (() => listBoxServers.Items.Clear()));
1 голос
/ 13 декабря 2010

Использование Invoke в проекте Windows Forms может быть немного сложнее, есть некоторые подводные камни, которые задокументированы, но их легко пропустить. Я рекомендую использовать что-то похожее на этот вопрос:

Целесообразно ли расширить Control для обеспечения неизменно безопасной функциональности Invoke / BeginInvoke?

Он обрабатывает случаи, когда invoke не требуется, вызывается из разных потоков, обрабатывается или не создается, 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();
    }
}
1 голос
/ 13 декабря 2010

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

Создать метод, который будет вызывать UpdateServerDetails в основном потоке, например:

private void DispatchServerDetails(List<ServerDetails> details)
{
  Action<List<ServerDetails>> action = UpdateServerDetails;
  Dispatcher.Invoke(action)
}

, а затем вызовите DispatchServerDetails вместо UpdateServerDetails.

Некоторые предостережения:
- Это лучше всего работает в приложениях WPF, для WinForms выВам нужно будет перепрыгнуть через несколько обручей, или вы можете использовать InvokeRequired
- Обновление пользовательского интерфейса все еще происходит синхронно, поэтому, если UpdateServerDetails выполняет большую часть работы, он заблокирует поток пользовательского интерфейса (не ваш случайпросто чтобы быть в безопасности).

0 голосов
/ 17 июля 2015

Я только что придумал более простой способ без использования 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
}
...