Productividad y eficiencia desarrollando en .NET
Nota: Ejemplo completo de uso del control BackgroundWorker. El artículo inicial se creó en inglés y fue publicado aquí. En vista del éxito que ha tenido lo traduzco al castellano y lo republico también en este apartado.
Introducción
El control BackgroundWorker fue introducido en el .NET Framework 2.0 y permite realizar operaciones costosas o duraderas en un hilo diferente al del interfaz, de manera que la aplicación sigue respondiendo al usuario mientras este trabajo se procesa en segundo plano.
Usando este control, la gestión de hilos está encapsulada en el control de manera que el programador no tiene que lidiar con hilos (threads), invokes o delegados (delegates).
Ejemplo
Este sencillo ejemplo cubre prácticamente todas las posibilidades de este componente: soporte de cancelación, gestión de errores , información de progreso (y ejemplo de como pasar información en cada notificación de progreso)
Evento backgroundWorker1_DoWork
El evento backgroundWorker1_DoWork es desencadenado por el control cuando
el método RunWorkerAsync()es invocado. Este método se ejecuta en un segundo hilo que crea el control BackgroundWorker. Al no ser el hilo principal del interfaz, no intentes acceder a ningún control aquí, o si lo haces utiliza para ello un delegado.
01 | //[System.Diagnostics.DebuggerNonUserCodeAttribute()] (*) |
02 | private void backgroundWorker1_DoWork( object sender, DoWorkEventArgs e) |
03 | { |
04 | //no usaremos código try/catch aquí, a no ser que después hagamos un throw de la excepción capturada. |
05 | //es necesario dejar que el backgroundworker sea quien capture cualquier excepción producida. |
06 | //si se produce una excepción, el control la disponibilizará una vez haya finalizado su ejecución, |
07 | //y disparado el evento "backgroundWorker1_RunWorkerCompleted" |
08 | //the RunWorkerCompletedEventArgs object, method backgroundWorker1_RunWorkerCompleted |
09 | //try |
10 | //{ |
11 | DateTime start = DateTime.Now; |
12 | e.Result = "" ; |
13 | for ( int i = 0; i < 100; i++) |
14 | { |
15 | System.Threading.Thread.Sleep(50); //simulamos trabajo |
16 |
17 | //hemos completado un porcentaje del trabajo previsto, luego notificamos de ello. |
18 | backgroundWorker1.ReportProgress(i, DateTime.Now); |
19 |
20 | //descomenta este código para ver como esta excepción es gestionada por el |
21 | //control backgroundworker |
22 | //descomenta también el atributo indicado arriba para evitar que el depurador |
23 | //pare en la excepción, ya que queremos simular |
24 | //el comportamiento del control en tiempo de ejecución. |
25 | //if (i == 34) |
26 | // throw new Exception("something wrong here!!"); |
27 |
28 | //en caso de que soporte cancelación y el control haya recibido la petición de cancelación, |
29 | //cancelamos el trabajo. Es la manera en la que realizamos una salida "limpia". |
30 | if (backgroundWorker1.CancellationPending) |
31 | { |
32 | e.Cancel = true ; |
33 | return ; |
34 | } |
35 | } |
36 |
37 | TimeSpan duration = DateTime.Now - start; |
38 |
39 | //aquí podríamos devolver información de utilidad, como el resultado de un cálculo, |
40 | //número de elementos afectados, etc.. de manera sencilla y segura |
41 | //al hilo principal |
42 | e.Result = "Duration: " + duration.TotalMilliseconds.ToString() + " ms." ; |
43 | //} |
44 | //catch(Exception ex){ |
45 | // MessageBox.Show("Don't use try catch here!"); |
46 | //} |
47 | } |
(*) Nota: El atributo [System.Diagnostics.DebuggerNonUserCodeAttribute()] es necesario para evitar que el depurador interrumpa el flujo de la ejecución en caso de que una excepción se produzca. De esta manera podemos trabajar en modo depuración con el mismo comportamiento que el control tendría en modo ejecución. Este comportamiento consistiría en capturar internamente la excepción (por eso no usaremos try/catch en el método) y devolvernos la misma junto con el resultado, en el método backgroundWorker1_RunWorkerCompleted
Evento backgroundWorker1_ProgressChanged
Este evento es lanzado en el hilo principal, por lo que aquí SI podemos acceder a controles del formulario de manera segura.
01 | private void backgroundWorker1_ProgressChanged( object sender, |
02 | ProgressChangedEventArgs e) |
03 | { |
04 | progressBar1.Value = e.ProgressPercentage; //actualizamos la barra de progreso |
05 | DateTime time = Convert.ToDateTime(e.UserState); //obtenemos información adicional si procede |
06 |
07 | //en este ejemplo, logamos a un textbox |
08 | txtOutput.AppendText(time.ToLongTimeString()); |
09 | txtOutput.AppendText(Environment.NewLine); |
10 | } |
Evento backgroundWorker1_RunWorkerCompleted
Este método se ejecuta cuando la tarea ha sido finalizada. Una tarea se finaliza cuando:
a) termina de manera normal
b) termina con error
c) es cancelada.
En el objeto de tipo RunWorkerCompletedEventArgs encontraremos información detallada del tipo de finalización, así como los datos que hemos podido pasar en la propiedad Result.
01 | private void backgroundWorker1_RunWorkerCompleted( object sender, |
02 | RunWorkerCompletedEventArgs e) |
03 | { |
04 | if (e.Cancelled) { |
05 | MessageBox.Show( "The task has been cancelled" ); |
06 | } |
07 | else if (e.Error != null ) |
08 | { |
09 | MessageBox.Show( "Error. Details: " + (e.Error as Exception).ToString()); |
10 | } |
11 | else { |
12 | MessageBox.Show( "The task has been completed. Results: " + e.Result.ToString()); |
13 | } |
14 | } |
Enviado las órdenes de inicio y cancelación al control
Notificamos al control backgroundworker de que cancele el proceso.
1 | private void btoCancel_Click( object sender, EventArgs e) |
2 | { |
3 | //este código no mata ni afecta al hilo en el que se está ejecutando el procesamiento. |
4 | //Sirve a efectos de notificación, que debe de ser |
5 | //gestionada de la manera que se indica arriba en el ejemplo |
6 | backgroundWorker1.CancelAsync(); |
7 | } |
Lanzamos el trabajo. En este momento, el control backgroundworker lanza un hilo en el que ejecuta la tarea asignada, a través del método backgroundWorker1_DoWork visto anteriormente.
1 | private void btoStart_Click( object sender, EventArgs e) |
2 | { |
3 | backgroundWorker1.RunWorkerAsync(); |
4 | } |
No comments:
Post a Comment