Acessando o controle da interface do usuário do segmento BackgroundWorker

Eu tenho um botão no meu formulário do windows que chama o método RunWorkerAsync (), este por sua vez, executa uma ação que atualiza um ListBox no mesmo formulário.

Após o término do evento DoWork, atribuo o resultado para o evento (que é uma lista), processo o evento RunWorkerCompleted () e, em seguida, executo o código a seguir para atualizar minha checkbox de listview.

texto alternativo

que chama isso:

texto alternativo

(Desculpas, a formatação de código não funciona)

Agora, quando executo o aplicativo e pressiono o botão Atualizar, aparece a seguinte exceção:

texto alternativo

Como eu poderia contornar isso?

Editar:

A exceção é lançada na declaração seguinte, isso ocorre no método DoWork, onde eu limpo o conteúdo para manter a lista atualizada;

listBoxServers.Items.Clear ();

Aqui está um trecho que acho muito útil:

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

Você pode passar qualquer delegado do tipo Action ou simplesmente um lambda assim:

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

ou

 ThreadSafe(listBoxServers.Items.Clear); 

Você não pode chamar Invoke na checkbox de listview, mas no formulário. Para aplicativos WinForms eu uso algo como:

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

Dependendo da versão .NET, você pode ter que declarar um delegado para o MethodInvoker como

 public delegate void MethodInvoker(); 

No entanto, você também pode considerar o uso do recurso ReportProgress do Background Worker. O respectivo manipulador de events deve ser chamado no contexto do encadeamento do formulário.

O que eu fiz é algo assim cada vez que você precisa executar algo através de tópicos:

 listBoxServers.BeginInvoke( (Action) (() => listBoxServers.Items.Clear())); 

Encadeamentos em segundo plano não têm permissão para atualizar a interface do usuário em aplicativos do Windows, portanto, você precisa reverter o controle de volta ao thread da interface do usuário para a atualização real.

Crie um método que irá chamar UpdateServerDetails no thread principal, como este:

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

e, em seguida, chamar DispatchServerDetails vez de UpdateServerDetails .

Algumas ressalvas:
-Isso funciona melhor em aplicativos WPF, para WinForms, você precisará passar por alguns obstáculos ou usar InvokeRequired
-A atualização da interface do usuário ainda é síncrona, portanto, se UpdateServerDetails fizer muito trabalho, bloqueará o thread da interface do usuário (não o seu caso, apenas para estar no lado seguro).

Usando Invoke em um projeto de formulários do Windows pode ser um pouco complicado, existem algumas armadilhas que são documentadas, mas fáceis de perder. Eu recomendo usar algo como você encontrará nesta pergunta:

É apropriado estender o Controle para fornecer uma funcionalidade Invoke / BeginInvoke consistente e segura?

Ele lida com casos em que invocação não é necessária, é chamado de diferentes encadeamentos, identificador é ou não criado, etcetcetc. Ele pode ser facilmente modificado para SafeInvoke() e SafeBeginInvoke() se você não for um fã do parâmetro bool.

(Incluído aqui para sua conveniência:

 /// 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); ///  /// Execute a method on the control's owning thread. ///  /// The control that is being updated. /// The method that updates uiElement. /// 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. 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(); } } 

Acabei de descobrir uma maneira mais simples sem usar o 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); 

Em seguida, em backgroundworker1_ProgressChanged (remetente do object, ProgressChangedEventArgs e):

 if (e.ProgressPercentage < 0) { //access your ui control safely here }