Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Si tiene una operación que tardará mucho tiempo en completarse y no desea que la interfaz de usuario (UI) deje de responder o bloquearse, puede usar la BackgroundWorker clase para ejecutar la operación en otro subproceso.
En este tutorial se muestra cómo usar la BackgroundWorker clase para realizar cálculos lentos "en segundo plano", mientras que la interfaz de usuario sigue respondiendo. Cuando hayas terminado, tendrás una aplicación que calcula los números de Fibonacci de forma asincrónica. Aunque calcular un gran número de Fibonacci puede tardar una cantidad notable de tiempo, el subproceso principal de la interfaz de usuario no se interrumpirá con este retraso y el formulario tendrá capacidad de respuesta durante el cálculo.
Las tareas que se muestran en este tutorial incluyen:
Creación de una aplicación basada en Windows
Crear un BackgroundWorker en el formulario
Agregar controladores de eventos asincrónicos
Agregar informes de progreso y soporte para la cancelación
Para obtener una lista completa del código usado en este ejemplo, vea Cómo: Implementar un formulario que usa una operación en segundo plano.
Crear un formulario que use una operación en segundo plano
En Visual Studio, cree un proyecto de aplicación basado en Windows denominado
BackgroundWorkerExample(Archivo>Nuevo>Proyecto>Visual C# o Visual Basic>Aplicación de Windows Forms para Escritorio Clásico>).En el Explorador de soluciones, haga clic con el botón derecho en Form1 y seleccione Cambiar nombre en el menú contextual. Cambie el nombre del archivo a
FibonacciCalculator. Haga clic en el botón Sí cuando se le pregunte si desea cambiar el nombre de todas las referencias al elemento de código "Form1".Arrastre un NumericUpDown control desde el Cuadro de herramientas hasta el formulario. Establezca la propiedad Minimum en
1y la propiedad Maximum en91.Agregue dos Button controles al formulario.
Cambie el nombre del primer control Button a
startAsyncButtony establezca la propiedad Text enStart Async. Cambie el nombre del segundo control Button acancelAsyncButton, y establezca la propiedad Text enCancel Async. Establezca la propiedad Enabled enfalse.Cree un controlador de eventos para los eventos de ambos controles ButtonClick. Para obtener más información, vea Cómo: Crear controladores de eventos usando el diseñador.
Arrastre un Label control desde el Cuadro de herramientas hasta el formulario y cámbielo
resultLabel.Arrastre un ProgressBar control desde el Cuadro de herramientas hasta el formulario.
Creación de un BackgroundWorker con el Diseñador
Puede crear el BackgroundWorker para su operación asincrónica utilizando el Diseñador de WindowsForms.
En la pestaña Componentes del Cuadro de herramientas, arrastre un BackgroundWorker elemento al formulario.
Adición de controladores de eventos asincrónicos
Ya está listo para agregar controladores de eventos para los eventos asincrónicos del componente BackgroundWorker. Uno de estos controladores de eventos llama a la operación que requiere mucho tiempo, la cual se ejecutará en segundo plano y calcula los números de Fibonacci.
En la ventana Propiedades , con el BackgroundWorker componente aún seleccionado, haga clic en el botón Eventos . Haga doble clic en los eventos DoWork y RunWorkerCompleted para crear controladores de eventos. Para obtener más información sobre cómo usar controladores de eventos, vea How to: Create Event Handlers Using the Designer.
Cree un nuevo método, denominado
ComputeFibonacci, en el formulario. Este método realiza el trabajo real y se ejecutará en segundo plano. Este código muestra la implementación recursiva del algoritmo Fibonacci, que es especialmente ineficaz, tardando exponencialmente más tiempo en completarse para números mayores. Se usa aquí para fines ilustrativos, para mostrar una operación que puede presentar retrasos largos en la aplicación.// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. long ComputeFibonacci( int n, BackgroundWorker^ worker, DoWorkEventArgs ^ e ) { // The parameter n must be >= 0 and <= 91. // Fib(n), with n > 91, overflows a long. if ( (n < 0) || (n > 91) ) { throw gcnew ArgumentException( "value must be >= 0 and <= 91","n" ); } long result = 0; // Abort the operation if the user has cancelled. // Note that a call to CancelAsync may have set // CancellationPending to true just after the // last invocation of this method exits, so this // code will not have the opportunity to set the // DoWorkEventArgs.Cancel flag to true. This means // that RunWorkerCompletedEventArgs.Cancelled will // not be set to true in your RunWorkerCompleted // event handler. This is a race condition. if ( worker->CancellationPending ) { e->Cancel = true; } else { if ( n < 2 ) { result = 1; } else { result = ComputeFibonacci( n - 1, worker, e ) + ComputeFibonacci( n - 2, worker, e ); } // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if ( percentComplete > highestPercentageReached ) { highestPercentageReached = percentComplete; worker->ReportProgress( percentComplete ); } } return result; }// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. long ComputeFibonacci(int n, BackgroundWorker worker, DoWorkEventArgs e) { // The parameter n must be >= 0 and <= 91. // Fib(n), with n > 91, overflows a long. if ((n < 0) || (n > 91)) { throw new ArgumentException( "value must be >= 0 and <= 91", "n"); } long result = 0; // Abort the operation if the user has canceled. // Note that a call to CancelAsync may have set // CancellationPending to true just after the // last invocation of this method exits, so this // code will not have the opportunity to set the // DoWorkEventArgs.Cancel flag to true. This means // that RunWorkerCompletedEventArgs.Cancelled will // not be set to true in your RunWorkerCompleted // event handler. This is a race condition. if (worker.CancellationPending) { e.Cancel = true; } else { if (n < 2) { result = 1; } else { result = ComputeFibonacci(n - 1, worker, e) + ComputeFibonacci(n - 2, worker, e); } // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); } } return result; }' This is the method that does the actual work. For this ' example, it computes a Fibonacci number and ' reports progress as it does its work. Function ComputeFibonacci( _ ByVal n As Integer, _ ByVal worker As BackgroundWorker, _ ByVal e As DoWorkEventArgs) As Long ' The parameter n must be >= 0 and <= 91. ' Fib(n), with n > 91, overflows a long. If n < 0 OrElse n > 91 Then Throw New ArgumentException( _ "value must be >= 0 and <= 91", "n") End If Dim result As Long = 0 ' Abort the operation if the user has canceled. ' Note that a call to CancelAsync may have set ' CancellationPending to true just after the ' last invocation of this method exits, so this ' code will not have the opportunity to set the ' DoWorkEventArgs.Cancel flag to true. This means ' that RunWorkerCompletedEventArgs.Cancelled will ' not be set to true in your RunWorkerCompleted ' event handler. This is a race condition. If worker.CancellationPending Then e.Cancel = True Else If n < 2 Then result = 1 Else result = ComputeFibonacci(n - 1, worker, e) + _ ComputeFibonacci(n - 2, worker, e) End If ' Report progress as a percentage of the total task. Dim percentComplete As Integer = _ CSng(n) / CSng(numberToCompute) * 100 If percentComplete > highestPercentageReached Then highestPercentageReached = percentComplete worker.ReportProgress(percentComplete) End If End If Return result End FunctionEn el DoWork controlador de eventos, agregue una llamada al
ComputeFibonaccimétodo . Tome el primer parámetro paraComputeFibonaccide la propiedad Argument del DoWorkEventArgs. Los parámetros BackgroundWorker y DoWorkEventArgs se usarán más adelante para el reporte de progreso y compatibilidad con la cancelación. Asigne el valor devuelto deComputeFibonaccia la propiedad Result del DoWorkEventArgs. Este resultado estará disponible para el controlador de RunWorkerCompleted eventos.Nota:
El DoWork controlador de eventos no hace referencia directamente a la
backgroundWorker1variable de instancia, ya que esto acoplaría este controlador de eventos a una instancia específica de BackgroundWorker. En su lugar, se recupera una referencia al BackgroundWorker que provocó este evento desde el parámetrosender. Esto es importante cuando el formulario hospeda más de un BackgroundWorker. También es importante no manipular ningún objeto de interfaz de usuario en el DoWork controlador de eventos. En su lugar, comuníquese con la interfaz de usuario a través de los BackgroundWorker eventos.// This event handler is where the actual, // potentially time-consuming work is done. void backgroundWorker1_DoWork( Object^ sender, DoWorkEventArgs^ e ) { // Get the BackgroundWorker that raised this event. BackgroundWorker^ worker = dynamic_cast<BackgroundWorker^>(sender); // Assign the result of the computation // to the Result property of the DoWorkEventArgs // object. This is will be available to the // RunWorkerCompleted eventhandler. e->Result = ComputeFibonacci( safe_cast<Int32>(e->Argument), worker, e ); }// This event handler is where the actual, // potentially time-consuming work is done. private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { // Get the BackgroundWorker that raised this event. BackgroundWorker worker = sender as BackgroundWorker; // Assign the result of the computation // to the Result property of the DoWorkEventArgs // object. This is will be available to the // RunWorkerCompleted eventhandler. e.Result = ComputeFibonacci((int)e.Argument, worker, e); }' This event handler is where the actual work is done. Private Sub backgroundWorker1_DoWork( _ ByVal sender As Object, _ ByVal e As DoWorkEventArgs) _ Handles backgroundWorker1.DoWork ' Get the BackgroundWorker object that raised this event. Dim worker As BackgroundWorker = _ CType(sender, BackgroundWorker) ' Assign the result of the computation ' to the Result property of the DoWorkEventArgs ' object. This is will be available to the ' RunWorkerCompleted eventhandler. e.Result = ComputeFibonacci(e.Argument, worker, e) End SubEn el
startAsyncButtoncontrolador de eventos del Click control, agregue el código que inicia la operación asincrónica.void startAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ ) { // Reset the text in the result label. resultLabel->Text = String::Empty; // Disable the UpDown control until // the asynchronous operation is done. this->numericUpDown1->Enabled = false; // Disable the Start button until // the asynchronous operation is done. this->startAsyncButton->Enabled = false; // Enable the Cancel button while // the asynchronous operation runs. this->cancelAsyncButton->Enabled = true; // Get the value from the UpDown control. numberToCompute = (int)numericUpDown1->Value; // Reset the variable for percentage tracking. highestPercentageReached = 0; // Start the asynchronous operation. backgroundWorker1->RunWorkerAsync( numberToCompute ); }private void startAsyncButton_Click(System.Object sender, System.EventArgs e) { // Reset the text in the result label. resultLabel.Text = String.Empty; // Disable the UpDown control until // the asynchronous operation is done. this.numericUpDown1.Enabled = false; // Disable the Start button until // the asynchronous operation is done. this.startAsyncButton.Enabled = false; // Enable the Cancel button while // the asynchronous operation runs. this.cancelAsyncButton.Enabled = true; // Get the value from the UpDown control. numberToCompute = (int)numericUpDown1.Value; // Reset the variable for percentage tracking. highestPercentageReached = 0; // Start the asynchronous operation. backgroundWorker1.RunWorkerAsync(numberToCompute); }Private Sub startAsyncButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles startAsyncButton.Click ' Reset the text in the result label. resultLabel.Text = [String].Empty ' Disable the UpDown control until ' the asynchronous operation is done. Me.numericUpDown1.Enabled = False ' Disable the Start button until ' the asynchronous operation is done. Me.startAsyncButton.Enabled = False ' Enable the Cancel button while ' the asynchronous operation runs. Me.cancelAsyncButton.Enabled = True ' Get the value from the UpDown control. numberToCompute = CInt(numericUpDown1.Value) ' Reset the variable for percentage tracking. highestPercentageReached = 0 ' Start the asynchronous operation. backgroundWorker1.RunWorkerAsync(numberToCompute) End SubEn el RunWorkerCompleted controlador de eventos, asigne el resultado del cálculo al
resultLabelcontrol .// This event handler deals with the results of the // background operation. void backgroundWorker1_RunWorkerCompleted( Object^ /*sender*/, RunWorkerCompletedEventArgs^ e ) { // First, handle the case where an exception was thrown. if ( e->Error != nullptr ) { MessageBox::Show( e->Error->Message ); } else if ( e->Cancelled ) { // Next, handle the case where the user cancelled // the operation. // Note that due to a race condition in // the DoWork event handler, the Cancelled // flag may not have been set, even though // CancelAsync was called. resultLabel->Text = "Cancelled"; } else { // Finally, handle the case where the operation // succeeded. resultLabel->Text = e->Result->ToString(); } // Enable the UpDown control. this->numericUpDown1->Enabled = true; // Enable the Start button. startAsyncButton->Enabled = true; // Disable the Cancel button. cancelAsyncButton->Enabled = false; }// This event handler deals with the results of the // background operation. private void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { // First, handle the case where an exception was thrown. if (e.Error != null) { MessageBox.Show(e.Error.Message); } else if (e.Cancelled) { // Next, handle the case where the user canceled // the operation. // Note that due to a race condition in // the DoWork event handler, the Cancelled // flag may not have been set, even though // CancelAsync was called. resultLabel.Text = "Canceled"; } else { // Finally, handle the case where the operation // succeeded. resultLabel.Text = e.Result.ToString(); } // Enable the UpDown control. this.numericUpDown1.Enabled = true; // Enable the Start button. startAsyncButton.Enabled = true; // Disable the Cancel button. cancelAsyncButton.Enabled = false; }' This event handler deals with the results of the ' background operation. Private Sub backgroundWorker1_RunWorkerCompleted( _ ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _ Handles backgroundWorker1.RunWorkerCompleted ' First, handle the case where an exception was thrown. If (e.Error IsNot Nothing) Then MessageBox.Show(e.Error.Message) ElseIf e.Cancelled Then ' Next, handle the case where the user canceled the ' operation. ' Note that due to a race condition in ' the DoWork event handler, the Cancelled ' flag may not have been set, even though ' CancelAsync was called. resultLabel.Text = "Canceled" Else ' Finally, handle the case where the operation succeeded. resultLabel.Text = e.Result.ToString() End If ' Enable the UpDown control. Me.numericUpDown1.Enabled = True ' Enable the Start button. startAsyncButton.Enabled = True ' Disable the Cancel button. cancelAsyncButton.Enabled = False End Sub
Agregar informes de progreso y soporte para la cancelación
En el caso de las operaciones asincrónicas que tardarán mucho tiempo, a menudo es conveniente notificar el progreso al usuario y permitir que el usuario cancele la operación. La BackgroundWorker clase proporciona un evento que te permite publicar el progreso a medida que avanza la operación en segundo plano. También proporciona una marca que permite al código de trabajo detectar una llamada a CancelAsync y interrumpirse.
Implementación de informes de progreso
En la ventana Propiedades, seleccione
backgroundWorker1. Establezca las WorkerReportsProgress propiedades y WorkerSupportsCancellation entrue.Declara dos variables en la sintaxis
FibonacciCalculator. Se usarán para realizar un seguimiento del progreso.int numberToCompute; int highestPercentageReached;private int numberToCompute = 0; private int highestPercentageReached = 0;Private numberToCompute As Integer = 0 Private highestPercentageReached As Integer = 0Agregue un controlador de eventos para el ProgressChanged evento. En el controlador de eventos ProgressChanged, actualice ProgressBar con la propiedad ProgressPercentage del parámetro ProgressChangedEventArgs.
// This event handler updates the progress bar. void backgroundWorker1_ProgressChanged( Object^ /*sender*/, ProgressChangedEventArgs^ e ) { this->progressBar1->Value = e->ProgressPercentage; }// This event handler updates the progress bar. private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; }' This event handler updates the progress bar. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.progressBar1.Value = e.ProgressPercentage End Sub
Implementación de soporte para cancelar
En el
cancelAsyncButtoncontrolador de eventos del Click control, agregue el código que cancela la operación asincrónica.void cancelAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ ) { // Cancel the asynchronous operation. this->backgroundWorker1->CancelAsync(); // Disable the Cancel button. cancelAsyncButton->Enabled = false; }private void cancelAsyncButton_Click(System.Object sender, System.EventArgs e) { // Cancel the asynchronous operation. this.backgroundWorker1.CancelAsync(); // Disable the Cancel button. cancelAsyncButton.Enabled = false; }Private Sub cancelAsyncButton_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles cancelAsyncButton.Click ' Cancel the asynchronous operation. Me.backgroundWorker1.CancelAsync() ' Disable the Cancel button. cancelAsyncButton.Enabled = False End SubLos siguientes fragmentos de código en el método
ComputeFibonaccinotifican el progreso y admiten la cancelación.if ( worker->CancellationPending ) { e->Cancel = true; }if (worker.CancellationPending) { e.Cancel = true; }If worker.CancellationPending Then e.Cancel = True// Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if ( percentComplete > highestPercentageReached ) { highestPercentageReached = percentComplete; worker->ReportProgress( percentComplete ); }// Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); }' Report progress as a percentage of the total task. Dim percentComplete As Integer = _ CSng(n) / CSng(numberToCompute) * 100 If percentComplete > highestPercentageReached Then highestPercentageReached = percentComplete worker.ReportProgress(percentComplete) End If
Punto de control
En este momento, puede compilar y ejecutar la aplicación calculadora de Fibonacci.
Presione F5 para compilar y ejecutar la aplicación.
Mientras el cálculo se ejecuta en segundo plano, verá que el ProgressBar muestra el progreso del cálculo hacia la finalización. También puede cancelar la operación pendiente.
Para números pequeños, el cálculo debe ser muy rápido, pero para números mayores, debería ver un retraso notable. Si escribe un valor de 30 o superior, debería ver un retraso de varios segundos, dependiendo de la velocidad del equipo. Para los valores mayores de 40, puede tardar minutos o horas en finalizar el cálculo. Aunque la calculadora está ocupada calculando un gran número de Fibonacci, observe que puede mover libremente el formulario alrededor, minimizar, maximizar e incluso descartarlo. Esto se debe a que el subproceso principal de la interfaz de usuario no está esperando a que finalice el cálculo.
Pasos siguientes
Ahora que ha implementado un formulario que usa un BackgroundWorker componente para ejecutar un cálculo en segundo plano, puede explorar otras posibilidades para las operaciones asincrónicas:
Use varios BackgroundWorker objetos para varias operaciones simultáneas.
Para depurar la aplicación multiproceso, consulte Cómo: Usar la ventana Subprocesos.
Implemente su propio componente que admita el modelo de programación asincrónica. Para obtener más información, consulte Introducción al patrón asincrónico basado en eventos.
Precaución
Al usar multithreading de cualquier tipo, puede exponerse a errores muy graves y complejos. Consulte los procedimientos recomendados de subprocesos administrados antes de implementar cualquier solución que use multithreading.
Consulte también
- System.ComponentModel.BackgroundWorker
- Subprocesamiento gestionado
- Procedimientos recomendados para el subprocesamiento administrado
- Introducción al patrón asincrónico basado en eventos
- Cómo: Implementar un formulario que usa una operación en segundo plano
- Tutorial: Ejecución de una operación en segundo plano
- Componente BackgroundWorker
.NET Desktop feedback