Dela via


Genomgång: Implementera ett formulär som använder en bakgrundsåtgärd

Om du har en åtgärd som tar lång tid att slutföra och du inte vill att användargränssnittet ska sluta svara eller blockera kan du använda BackgroundWorker klassen för att köra åtgärden på en annan tråd.

Den här genomgången BackgroundWorker visar hur du använder klassen för att utföra tidskrävande beräkningar "i bakgrunden", medan användargränssnittet förblir responsivt. När du är klar har du ett program som beräknar Fibonacci-tal asynkront. Även om det kan ta en märkbar tid att beräkna ett stort Fibonacci-nummer avbryts inte huvudtråden i användargränssnittet av den här fördröjningen, och formuläret svarar under beräkningen.

Uppgifter som illustreras i den här genomgången är:

  • Skapa ett Windows-baserat program

  • Skapa en BackgroundWorker i formuläret

  • Lägga till asynkrona händelsehanterare

  • Lägga till förloppsrapportering och support för annullering

En fullständig lista över koden som används i det här exemplet finns i Så här: Implementera ett formulär som använder en bakgrundsåtgärd.

Skapa ett formulär som använder en bakgrundsåtgärd

  1. I Visual Studio skapar du ett Windows-baserat programprojekt med namnet BackgroundWorkerExample (File>New>Project>Visual C# eller Visual Basic>Classic Desktop>Windows Forms Application).

  2. Högerklicka på Formulär1 i Solution Explorer och välj Byt namn på snabbmenyn. Ändra filnamnet till FibonacciCalculator. Klicka på knappen Ja när du tillfrågas om du vill byta namn på alla referenser till kodelementetForm1.

  3. Dra en NumericUpDown kontroll från verktygslådan till formuläret. Ange egenskapen Minimum till 1 och egenskapen Maximum till 91.

  4. Lägg till två Button kontroller i formuläret.

  5. Byt namn på den första Button kontrollen startAsyncButton och ange egenskapen Text till Start Async. Byt namn på den andra Button-kontrollen till cancelAsyncButton, och ange Text-egenskapen till Cancel Async. Ange egenskapen Enabled till false.

  6. Skapa en händelsehanterare för båda Button kontrollernas Click händelser. Mer information finns i Så här skapar du händelsehanterare med hjälp av designern.

  7. Dra en Label kontroll från verktygslådan till formuläret och byt namn på den resultLabel.

  8. Dra en ProgressBar kontroll från verktygslådan till formuläret.

Skapa en BackgroundWorker med designern

Du kan skapa BackgroundWorker för din asynkrona åtgärd med hjälp av WindowsForms Designer.

Dra en till formuläret på fliken Komponenter i BackgroundWorker.

Lägga till asynkrona händelsehanterare

Nu är du redo att lägga till händelsehanterare för komponentens BackgroundWorker asynkrona händelser. Den tidskrävande åtgärd som körs i bakgrunden, som beräknar Fibonacci-tal, anropas av en av dessa händelsehanterare.

  1. I fönstret Egenskaper , med komponenten BackgroundWorker fortfarande markerad, klickar du på knappen Händelser . Dubbelklicka på DoWork händelserna och RunWorkerCompleted för att skapa händelsehanterare. Mer information om hur du använder händelsehanterare finns i Så här skapar du händelsehanterare med hjälp av designern.

  2. Skapa en ny metod med namnet ComputeFibonacci, i formuläret. Den här metoden utför det faktiska arbetet och körs i bakgrunden. Den här koden visar den rekursiva implementeringen av Fibonacci-algoritmen, som är särskilt ineffektiv, vilket tar exponentiellt längre tid att slutföra för större tal. Den används här i illustrativa syften för att visa en åtgärd som kan medföra långa fördröjningar i ditt program.

    // 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 Function
    
  3. DoWork Lägg till ett anrop till metoden i ComputeFibonacci händelsehanteraren. Ta den första parametern för ComputeFibonacci från Argument egenskapen för DoWorkEventArgs. Parametrarna BackgroundWorker och DoWorkEventArgs används senare för förloppsrapportering och support för annullering. Tilldela returvärdet från ComputeFibonacci till Result-egenskapen på DoWorkEventArgs. Det här resultatet blir tillgängligt för RunWorkerCompleted händelsehanteraren.

    Anmärkning

    Händelsehanteraren DoWork refererar inte direkt till backgroundWorker1 instansvariabeln, eftersom detta skulle koppla den här händelsehanteraren till en specifik instans av BackgroundWorker. I stället återställs en referens till den BackgroundWorker som skapade den här händelsen från parametern sender . Detta är viktigt när formuläret innehåller mer än en BackgroundWorker. Det är också viktigt att inte ändra några användargränssnittsobjekt i händelsehanteraren DoWork . Kommunicera i stället med användargränssnittet via BackgroundWorker händelserna.

    // 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 Sub
    
  4. I kontrollens startAsyncButtonClick händelsehanterare lägger du till koden som startar den asynkrona åtgärden.

    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 Sub 
    
  5. RunWorkerCompleted I händelsehanteraren tilldelar du resultatet av beräkningen till resultLabel kontrollen.

    // 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
    

Lägga till förloppsrapportering och support för annullering

För asynkrona åtgärder som tar lång tid är det ofta önskvärt att rapportera förloppet till användaren och låta användaren avbryta åtgärden. Klassen BackgroundWorker tillhandahåller en händelse som gör att du kan rapportera förloppet medan bakgrundsprocessen fortskrider. Den innehåller också en flagga som gör att din arbetskod kan identifiera ett anrop till CancelAsync och avbryta sig själv.

Implementera förloppsrapportering

  1. I fönstret Egenskaper väljer du backgroundWorker1. Ange egenskaperna WorkerReportsProgress och WorkerSupportsCancellation till true.

  2. Deklarera två variabler i formuläret FibonacciCalculator . Dessa används för att spåra förloppet.

    int numberToCompute;
    int highestPercentageReached;
    
    private int numberToCompute = 0;
    private int highestPercentageReached = 0;
    
    Private numberToCompute As Integer = 0
    Private highestPercentageReached As Integer = 0
    
  3. Lägg till en händelsehanterare för ProgressChanged händelsen. I händelsehanteraren ProgressChanged, uppdaterar du ProgressBar med ProgressPercentage-egenskapen av ProgressChangedEventArgs-parametern.

    // 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
    

Implementera stöd för annullering

  1. I kontrollens cancelAsyncButtonClick händelsehanterare lägger du till koden som avbryter den asynkrona åtgärden.

    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 Sub
    
  2. Följande kodfragment i ComputeFibonacci metoden rapporterar förlopp och stöd för annullering.

    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
    

Kontrollpunkt

Nu kan du kompilera och köra Fibonacci Calculator-programmet.

Tryck på F5 för att kompilera och köra programmet.

Medan beräkningen körs i bakgrunden, kommer du att se ProgressBar som visar beräkningens progression mot slutförande. Du kan också avbryta den pågående åtgärden.

För små tal bör beräkningen vara mycket snabb, men för större tal bör du se en märkbar fördröjning. Om du anger ett värde på 30 eller högre bör du se en fördröjning på flera sekunder, beroende på datorns hastighet. För värden som är större än 40 kan det ta minuter eller timmar att slutföra beräkningen. Medan kalkylatorn är upptagen med att beräkna ett stort Fibonacci-nummer, observera att du fritt kan flytta runt formuläret, minimera, maximera och till och med stänga det. Det beror på att huvudtråden för användargränssnittet inte väntar på att beräkningen ska slutföras.

Nästa steg

Nu när du har implementerat ett formulär som använder en BackgroundWorker komponent för att köra en beräkning i bakgrunden kan du utforska andra möjligheter för asynkrona åtgärder:

Se även