2011-03-04 15 views
6

He estado buscando y he encontrado que una buena manera de realizar trabajos en segundo plano y actualizar la GUI es utilizando trabajadores en segundo plano. Sin embargo, al hacer esta pequeña tarea (estúpida) (contando de 1 a 10000), ¡no actualiza el contenido de la etiqueta, sino que lo imprime en la depuración! (Esto es sólo una solución pico para otro proyecto, por supuesto ...)Actualizar GUI usando BackgroundWorker

Aquí está el código:

public partial class MainWindow : Window 
{ 
    BackgroundWorker bw = new BackgroundWorker(); 

    public MainWindow() 
    { 
     InitializeComponent(); 
    } 

    private void button1_Click(object sender, RoutedEventArgs e) 
    { 

     bw.DoWork += new DoWorkEventHandler(bw_DoWork); 
     bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); 
     bw.WorkerReportsProgress = true; 
     bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); 
     bw.RunWorkerAsync(); 

    } 

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     MessageBox.Show("DONE"); 

    } 

    void bw_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     label1.Content = "going here: "+e.ProgressPercentage; 
     Debug.WriteLine(e.ProgressPercentage); 
    } 

    void bw_DoWork(object sender, DoWorkEventArgs e) 
    { 
     for (int i=0; i < 10000; i++) 
     { 
      bw.ReportProgress((i*100)/10000); 
     } 
    } 

} 

Respuesta

12

ProgressChanged El evento se eleva sobre el hilo de interfaz de usuario, no el subproceso de trabajo. En su código, el subproceso de trabajo no hace casi nada (solo bucle de 0 a 10000 y llama al ReportProgress), la mayor parte del trabajo se realiza en el subproceso de la interfaz de usuario. Básicamente, estás enviando demasiadas notificaciones de progreso. Debido a esto, el hilo de la interfaz de usuario casi siempre está ocupado y no tiene tiempo para representar el nuevo contenido de la etiqueta.

La representación en WPF no se realiza inmediatamente cuando cambia una propiedad de un control, sino en un marco de despachador separado, que se procesa cuando el despachador no tiene nada más urgente que hacer, según la prioridad de la tarea. La prioridad utilizada para la representación tiene un valor de 7 (DispatcherPriority.Render); el evento ProgressChanged se dirige al subproceso de la interfaz de usuario con una prioridad de 9 (DispatcherPriority.Normal), as specified on MSDN. Por lo tanto, las notificaciones ProgressChanged siempre tienen una prioridad mayor que la representación y, como siguen apareciendo, el despachador nunca tiene tiempo para procesar las tareas de representación.

Si sólo disminuir la frecuencia de las notificaciones, su aplicación debería funcionar bien (en la actualidad va a enviar 100 notificaciones para cada valor porcentual, que es inútil):

void bw_DoWork(object sender, DoWorkEventArgs e) 
    { 
     for (int i = 0; i < 10000; i++) 
     { 
      if (i % 100 == 0) 
       bw.ReportProgress(i/100); 
     } 
    } 
+1

+1. Muy bien. Además, agregar 'Thread.Sleep (10);' ayudará. – Aliostad

+0

¡Believe or not, en realidad lo estaba pensando antes de ver tu comentario! Siente por completo que la UI se congela con tanto llama ... –

+2

Por supuesto, es más fácil vincularse a una propiedad y actualizarla desde el hilo de trabajo. – Stimul8d

0

intenta cambiar la etiqueta utilizando womething así:

string Text = "going here: "+ e.ProgressPercentage"; 
this.Invoke((MethodInvoker)delegate { 
    label1.Content = newText; 
}); 

Por favor, tenga en cuenta que no estoy seguro de que funcione. No puedo probarlo ahora. Si no funciona, avíseme y borraré la respuesta.

Gracias.

Si necesita la manera canónica de hacer exactamente lo que quiere, mira el Hath respuesta en este post: How to update the GUI from another thread in C#?

+0

Bueno ... Su respuesta podría funcionar si estuviera usando formularios de Windows ... en WPF no tengo la propiedad Invocar de los controles ... Solo el despachador del control como tal. También he estado tratando de "meterme" con eso, pero no tuve éxito :( –

+1

Puedes hacer lo mismo en WPF con Dispatcher.Invoke. Pero esto es irrelevante de todos modos: el evento ProgressChanged se genera en el hilo de UI, por lo que Invoke no es requerido. –

4
this.Dispatcher.BeginInvoke((Action) delegate(){ 
    label1.Content = "going here: "+e.ProgressPercentage; 
});