2010-12-13 14 views
9

Tengo un botón en mi formulario de Windows que llama al método RunWorkerAsync(), esto a su vez realiza una acción que luego actualiza un ListBox en el mismo formulario.Acceso al control de la interfaz de usuario desde BackgroundWorker Thread

Después del evento DoWork ha terminado me asigna el resultado para el evento (que es una lista), puedo procesar el evento RunWorkerCompleted() y luego realizar el siguiente código para actualizar mi cuadro de lista

alt text

el cual llama a esto:

alt text

(Disculpas, el formato de código no funcionará)

Ahora en que funciono la aplicación y pulsar el botón de actualización aparece la siguiente excepción:

alt text

¿Cómo voy a conseguir alrededor de esto?

Editar:

La excepción se produce en el estado de folowing, esto ocurre en el método DoWork donde puedo borrar los contenidos para mantener la lista actualizada;

listBoxServers.Items.Clear();

+0

¿Es este WPF o Windows Forms? ¿Y en qué línea obtienes esta excepción? – decyclone

+0

@decyclone He actualizado mi pregunta con más información –

+0

Recomiendo la respuesta de Chris. – decyclone

Respuesta

10

He aquí un fragmento que me parece muy útil:

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

Usted puede pasarlo cualquier delegado de Action tipo o simplemente una lambda como esto:

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

o

ThreadSafe(listBoxServers.Items.Clear); 
+1

¿No es este WPF? ¿WinForms es compatible con el Dispatcher? –

+0

'Dispatcher' es una clase WPF pero este código funcionará con WinForms. – Nobody

+0

Por alguna razón Dispatcher no es reconocido por IntelliSense, tengo la instrucción System.Threading incluida. –

1

Los subprocesos de fondo no pueden actualizar la interfaz de usuario en las aplicaciones de Windows, por lo que debe revertir el control al hilo de la interfaz de usuario para la actualización real.

crear un método que se llame UpdateServerDetails en el hilo principal, por ejemplo:

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

y luego llamar a DispatchServerDetails en lugar de UpdateServerDetails.

Algunas advertencias:
-Este funciona mejor en aplicaciones de WPF, para WinForms, tendrá que saltar a través de algunos aros, o puede utilizar InvokeRequired actualización
-La interfaz de usuario sigue siendo sincrónica, por lo que si hace un UpdateServerDetails Mucho trabajo, bloqueará el hilo de UI (no es tu caso, solo para estar seguro).

+0

¿Puedes dar un ejemplo? –

+0

@Jamie http://stackoverflow.com/questions/975087/wpf-dispatcher-and-running-it-in-background – jan

+1

¿No es una clase de WPF? – CodesInChaos

9

Lo que he hecho es algo como esto cada vez que se necesita para ejecutar algo a través de las discusiones:

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

Problema típico de Lisp, parece que falta un paren. –

+0

Gracias Hans, he agregado el paren perdido. –

13

No puede llamar al Invoke en el cuadro de lista, pero en el formulario.Para las aplicaciones de Windows Forms utilizo algo como:

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

Dependiendo de la versión de .NET, es posible que tenga que declarar un delegado para MethodInvoker sí mismo como

public delegate void MethodInvoker(); 

Sin embargo, también puede considerar el uso de la función de ReportProgress del trabajador de fondo. El controlador de evento respectivo debe invocarse en el contexto del hilo del formulario.

1

Usar Invoke en un proyecto de formularios de Windows puede ser un poco complicado, existen algunos inconvenientes que están documentados pero son fáciles de perder. Recomiendo el uso de algo así como que encontrará en esta pregunta:

Is it appropriate to extend Control to provide consistently safe Invoke/BeginInvoke functionality?

Se ocupa de los casos en que no se requiere la invocación, se llama desde diferentes hilos, mango es o no se crea, etcetcetc. Se puede modificar fácilmente para que sea SafeInvoke() y SafeBeginInvoke() si no es seguidor del parámetro bool.

(incluido aquí para su conveniencia:

/// 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(); 
    } 
} 
0

simplemente me di cuenta de una manera más sencilla sin necesidad de utilizar invocación:

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); 

Luego, en backgroundworker1_ProgressChanged (remitente del objeto, ProgressChangedEventArgs e):

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