Panduan: Menerapkan Formulir yang Menggunakan Operasi Latar Belakang
Jika Anda memiliki operasi yang akan memakan waktu lama untuk diselesaikan, dan Anda tidak ingin antarmuka pengguna (UI) berhenti merespons atau memblokir, Anda dapat menggunakan BackgroundWorker kelas untuk menjalankan operasi pada utas lain.
Panduan ini menggambarkan cara menggunakan BackgroundWorker kelas untuk melakukan komputasi yang memakan waktu "di latar belakang", sementara antarmuka pengguna tetap responsif. Ketika Anda selesai, Anda akan memiliki aplikasi yang menghitung angka Fibonacci secara asinkron. Meskipun komputasi nomor Fibonacci yang besar dapat memakan waktu yang nyata, utas UI utama tidak akan terganggu oleh penundaan ini, dan formulir akan responsif selama perhitungan.
Tugas yang diilustrasikan dalam panduan ini meliputi:
Membuat Aplikasi berbasis Windows
Membuat BackgroundWorker dalam Formulir Anda
Menambahkan Penanganan Aktivitas Asinkron
Menambahkan Pelaporan Kemajuan dan Dukungan untuk Pembatalan
Untuk daftar lengkap kode yang digunakan dalam contoh ini, lihat Cara: Menerapkan Formulir yang Menggunakan Operasi Latar Belakang.
Membuat formulir yang menggunakan operasi latar belakang
Di Visual Studio, buat proyek aplikasi berbasis Windows yang disebut
BackgroundWorkerExample
(File>New>Project>Visual C# atau Visual Basic>Classic Desktop> Formulir Windows Application).Di Penjelajah Solusi, klik kanan Formulir1 dan pilih Ganti Nama dari menu pintasan. Ubah nama file menjadi
FibonacciCalculator
. Klik tombol Ya saat Anda ditanya apakah Anda ingin mengganti nama semua referensi ke elemen kode 'Form1
'.NumericUpDown Seret kontrol dari Kotak Alat ke formulir. Atur Minimum properti ke
1
dan Maximum properti ke91
.Tambahkan dua Button kontrol ke formulir.
Ganti nama kontrol pertama Button dan atur properti ke Text
Start Async
.startAsyncButton
Ganti nama kontrol kedua Button , dan atur properti ke TextCancel Async
.cancelAsyncButton
Atur propertinya Enabled kefalse
.Buat penanganan aktivitas untuk kedua Button peristiwa kontrol Click . Untuk detailnya, lihat Cara: Membuat Penanganan Aktivitas Menggunakan Perancang.
Label Seret kontrol dari Kotak Alat ke formulir dan ganti namanya
resultLabel
.ProgressBar Seret kontrol dari Kotak Alat ke formulir.
Membuat BackgroundWorker dengan Perancang
Anda dapat membuat BackgroundWorker untuk operasi asinkron Anda menggunakan Windows Forms Designer.
Dari tab Komponen kotak Alat, seret BackgroundWorker ke formulir.
Menambahkan penanganan aktivitas asinkron
Anda sekarang siap untuk menambahkan penanganan aktivitas untuk BackgroundWorker peristiwa asinkron komponen. Operasi yang memakan waktu yang akan berjalan di latar belakang, yang menghitung angka Fibonacci, dipanggil oleh salah satu penanganan aktivitas ini.
Di jendela Properti , dengan BackgroundWorker komponen masih dipilih, klik tombol Peristiwa . DoWork Klik dua kali peristiwa dan RunWorkerCompleted untuk membuat penanganan aktivitas. Untuk informasi selengkapnya tentang cara menggunakan penanganan aktivitas, lihat Cara: Membuat Penanganan Aktivitas Menggunakan Perancang.
Buat metode baru, yang disebut
ComputeFibonacci
, dalam formulir Anda. Metode ini melakukan pekerjaan aktual, dan akan berjalan di latar belakang. Kode ini menunjukkan implementasi rekursif algoritma Fibonacci, yang terutama tidak efisien, membutuhkan waktu yang jauh lebih lama untuk diselesaikan untuk jumlah yang lebih besar. Ini digunakan di sini untuk tujuan ilustrasi, untuk menunjukkan operasi yang dapat memperkenalkan penundaan panjang dalam aplikasi Anda.// 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
Di penanganan DoWork aktivitas, tambahkan panggilan ke
ComputeFibonacci
metode . Ambil parameter pertama untukComputeFibonacci
dari Argument properti .DoWorkEventArgs Parameter BackgroundWorker dan DoWorkEventArgs akan digunakan nanti untuk pelaporan kemajuan dan dukungan pembatalan. Tetapkan nilai pengembalian dariComputeFibonacci
ke Result properti .DoWorkEventArgs Hasil ini akan tersedia untuk penanganan RunWorkerCompleted aktivitas.Catatan
Penanganan DoWork aktivitas tidak mereferensikan
backgroundWorker1
variabel instans secara langsung, karena ini akan menggabungkan penanganan aktivitas ini ke instans tertentu dari BackgroundWorker. Sebagai gantinya, referensi ke BackgroundWorker yang menaikkan peristiwa ini dipulihkan darisender
parameter . Ini penting ketika formulir menghosting lebih dari satu BackgroundWorker. Penting juga untuk tidak memanipulasi objek antarmuka pengguna apa pun di penanganan aktivitas Anda DoWork . Sebagai gantinya, komunikasikan ke antarmuka pengguna melalui BackgroundWorker peristiwa.// 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
Di penanganan
startAsyncButton
aktivitas kontrol Click , tambahkan kode yang memulai operasi asinkron.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
Di penanganan RunWorkerCompleted aktivitas, tetapkan hasil perhitungan ke
resultLabel
kontrol.// 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
Menambahkan Pelaporan Kemajuan dan Dukungan untuk Pembatalan
Untuk operasi asinkron yang akan memakan waktu lama, sering kali diinginkan untuk melaporkan kemajuan kepada pengguna dan untuk memungkinkan pengguna membatalkan operasi. Kelas ini BackgroundWorker menyediakan peristiwa yang memungkinkan Anda untuk memposting kemajuan saat operasi latar belakang Anda berlanjut. Ini juga menyediakan bendera yang memungkinkan kode pekerja Anda mendeteksi panggilan ke CancelAsync dan mengganggu dirinya sendiri.
Menerapkan pelaporan kemajuan
Di jendela Properti, pilih
backgroundWorker1
. Atur WorkerReportsProgress properti dan WorkerSupportsCancellation ketrue
.Deklarasikan dua variabel dalam
FibonacciCalculator
formulir. Ini akan digunakan untuk melacak kemajuan.int numberToCompute; int highestPercentageReached;
private int numberToCompute = 0; private int highestPercentageReached = 0;
Private numberToCompute As Integer = 0 Private highestPercentageReached As Integer = 0
Tambahkan penanganan aktivitas untuk peristiwa tersebut ProgressChanged . Di penanganan ProgressChanged aktivitas, perbarui ProgressBar dengan ProgressPercentage properti ProgressChangedEventArgs parameter .
// 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
Menerapkan dukungan untuk pembatalan
Di penanganan
cancelAsyncButton
aktivitas kontrol Click , tambahkan kode yang membatalkan operasi asinkron.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
Fragmen kode berikut dalam
ComputeFibonacci
metode melaporkan kemajuan dan mendukung pembatalan.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
Pos pemeriksaan
Pada titik ini, Anda dapat mengkompilasi dan menjalankan aplikasi Kalkulator Fibonacci.
Menekan F5 untuk mengompilasikan dan menjalankan aplikasi.
Saat perhitungan berjalan di latar belakang, Anda akan melihat ProgressBar menampilkan kemajuan perhitungan terhadap penyelesaian. Anda juga dapat membatalkan operasi yang tertunda.
Untuk jumlah kecil, perhitungannya harus sangat cepat, tetapi untuk jumlah yang lebih besar, Anda akan melihat penundaan yang terlihat. Jika Anda memasukkan nilai 30 atau lebih besar, Anda akan melihat penundaan beberapa detik, tergantung pada kecepatan komputer Anda. Untuk nilai yang lebih besar dari 40, mungkin perlu waktu beberapa menit atau jam untuk menyelesaikan perhitungan. Meskipun kalkulator sibuk menghitung jumlah Fibonacci besar, perhatikan bahwa Anda dapat dengan bebas memindahkan formulir, meminimalkan, memaksimalkan, dan bahkan menutupnya. Ini karena utas UI utama tidak menunggu penghitungan selesai.
Langkah berikutnya
Sekarang setelah Anda menerapkan formulir yang menggunakan BackgroundWorker komponen untuk menjalankan komputasi di latar belakang, Anda dapat menjelajahi kemungkinan lain untuk operasi asinkron:
Gunakan beberapa BackgroundWorker objek untuk beberapa operasi simultan.
Untuk men-debug aplikasi multithread Anda, lihat Cara: Menggunakan Jendela Utas.
Terapkan komponen Anda sendiri yang mendukung model pemrograman asinkron. Untuk informasi selengkapnya, lihat Gambaran Umum Pola Asinkron berbasis peristiwa.
Perhatian
Saat menggunakan multithreading dalam bentuk apa pun, Anda berpotensi mengekspos diri Anda ke bug yang sangat serius dan kompleks. Lihat Praktik Terbaik Utas Terkelola sebelum menerapkan solusi apa pun yang menggunakan multithreading.
Lihat juga
.NET Desktop feedback