Pertimbangkan metode hipotetis dari suatu objek yang melakukan hal-hal untuk Anda:
public class DoesStuff
{
BackgroundWorker _worker = new BackgroundWorker();
...
public void CancelDoingStuff()
{
_worker.CancelAsync();
//todo: Figure out a way to wait for BackgroundWorker to be cancelled.
}
}
Bagaimana seseorang bisa menunggu BackgroundWorker dilakukan?
Di masa lalu orang telah mencoba:
while (_worker.IsBusy)
{
Sleep(100);
}
Tapi ini kebuntuan , karena IsBusy
tidak dibersihkan sampai setelah RunWorkerCompleted
acara ditangani, dan kejadian itu tidak bisa ditangani sampai aplikasi idle. Aplikasi tidak akan menganggur sampai pekerja selesai. (Plus, ini adalah lingkaran sibuk - menjijikkan.)
Yang lain menambahkan saran untuk memasukkannya ke:
while (_worker.IsBusy)
{
Application.DoEvents();
}
Masalah dengan itu adalah yang Application.DoEvents()
menyebabkan pesan saat ini dalam antrian untuk diproses, yang menyebabkan masalah re-entrancy (.NET tidak kembali masuk).
Saya berharap dapat menggunakan beberapa solusi yang melibatkan objek sinkronisasi acara, di mana kode menunggu sebuah acara - yang RunWorkerCompleted
diatur oleh penangan acara pekerja . Sesuatu seperti:
Event _workerDoneEvent = new WaitHandle();
public void CancelDoingStuff()
{
_worker.CancelAsync();
_workerDoneEvent.WaitOne();
}
private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
_workerDoneEvent.SetEvent();
}
Tapi saya kembali ke jalan buntu: event handler tidak bisa berjalan sampai aplikasi idle, dan aplikasi tidak akan idle karena menunggu Event.
Jadi bagaimana Anda bisa menunggu BackgroundWorker selesai?
Pembaruan Orang-orang tampaknya bingung oleh pertanyaan ini. Mereka tampaknya berpikir bahwa saya akan menggunakan BackgroundWorker sebagai:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);
Artinya tidak , itu adalah tidak apa yang saya lakukan, dan itu tidak apa yang diminta di sini. Jika itu masalahnya, tidak akan ada gunanya menggunakan pekerja latar belakang.
sumber
((BackgroundWorker)sender).CancellationPending
untuk mendapatkan pembatalan acaraAda masalah dengan respons ini . UI perlu terus memproses pesan saat Anda menunggu, jika tidak, tidak akan dicat ulang, yang akan menjadi masalah jika pekerja latar belakang Anda membutuhkan waktu lama untuk menanggapi permintaan pembatalan.
Celah kedua adalah yang
_resetEvent.Set()
tidak akan pernah dipanggil jika utas pekerja melemparkan pengecualian - meninggalkan utas utama menunggu tanpa batas waktu - namun cacat ini dapat dengan mudah diperbaiki dengan blok coba / akhirnya.Salah satu cara untuk melakukan ini adalah dengan menampilkan dialog modal yang memiliki timer yang berulang kali memeriksa apakah pekerja latar belakang telah selesai bekerja (atau selesai membatalkan dalam kasus Anda). Setelah pekerja latar belakang selesai, dialog modal mengembalikan kontrol ke aplikasi Anda. Pengguna tidak dapat berinteraksi dengan UI sampai ini terjadi.
Metode lain (dengan asumsi Anda memiliki maksimum satu jendela modeless terbuka) adalah untuk mengatur ActiveForm.Enabled = false, kemudian loop pada Aplikasi, DoEvents sampai pekerja latar belakang selesai membatalkan, setelah itu Anda dapat mengatur ActiveForm.Enabled = true lagi.
sumber
Hampir Anda semua bingung dengan pertanyaan itu, dan tidak mengerti bagaimana seorang pekerja digunakan.
Pertimbangkan event handler RunWorkerComplete:
Dan semuanya baik-baik saja.
Sekarang tiba situasi di mana penelepon harus membatalkan penghitungan mundur karena mereka harus melakukan penghancuran roket secara otomatis.
Dan ada juga situasi di mana kita perlu membuka gerbang akses ke roket, tetapi tidak saat melakukan hitungan mundur:
Dan akhirnya, kita perlu mengurangi bahan bakar roket, tapi itu tidak diperbolehkan selama hitungan mundur:
Tanpa kemampuan untuk menunggu pekerja untuk membatalkan, kita harus memindahkan ketiga metode ke RunWorkerCompletedEvent:
Sekarang saya bisa menulis kode seperti itu, tetapi saya tidak akan melakukannya. Saya tidak peduli, saya hanya tidak.
sumber
Anda dapat memeriksa RunWorkerCompletedEventArgs di RunWorkerCompletedEventHandler untuk melihat statusnya. Berhasil, dibatalkan, atau kesalahan.
Pembaruan : Untuk melihat apakah pekerja Anda telah memanggil .CancelAsync () dengan menggunakan ini:
sumber
Anda tidak menunggu sampai pekerja latar belakang selesai. Itu cukup banyak mengalahkan tujuan meluncurkan utas terpisah. Sebagai gantinya, Anda harus membiarkan metode Anda selesai, dan memindahkan kode apa pun yang bergantung pada penyelesaian ke tempat lain. Anda membiarkan pekerja memberi tahu Anda ketika sudah selesai dan memanggil kode yang tersisa kemudian.
Jika Anda ingin menunggu sesuatu selesai menggunakan konstruk threading berbeda yang menyediakan WaitHandle.
sumber
Mengapa Anda tidak bisa hanya mengikat ke BackgroundWorker.RunWorkerCompleted Event. Ini adalah panggilan balik yang akan "Terjadi ketika operasi latar belakang selesai, telah dibatalkan, atau telah menimbulkan pengecualian."
sumber
Saya tidak mengerti mengapa Anda ingin menunggu BackgroundWorker selesai; sepertinya benar-benar kebalikan dari motivasi untuk kelas.
Namun, Anda bisa memulai setiap metode dengan panggilan ke pekerja. IsBusy dan minta mereka keluar jika sedang berjalan.
sumber
Hanya ingin mengatakan bahwa saya datang ke sini karena saya membutuhkan pekerja latar belakang untuk menunggu sementara saya menjalankan proses async sementara dalam satu lingkaran, perbaikan saya jauh lebih mudah daripada semua hal lain ini ^^
Baru saja tahu saya akan berbagi karena ini adalah tempat saya akhirnya mencari solusi. Juga, ini adalah posting pertama saya di stack overflow jadi jika itu buruk atau apa pun saya suka kritik! :)
sumber
Hm mungkin saya tidak menjawab pertanyaan Anda dengan benar.
Backgroundworker memanggil acara WorkerCompleted sekali 'workermethod' nya (metode / fungsi / sub yang menangani backgroundworker.doWork-event ) selesai sehingga tidak perlu untuk memeriksa apakah BW masih berjalan. Jika Anda ingin menghentikan pekerja Anda, periksa pembatalan properti tertunda di dalam 'metode pekerja' Anda.
sumber
Alur kerja suatu
BackgroundWorker
objek pada dasarnya mengharuskan Anda untuk menanganiRunWorkerCompleted
acara untuk kedua kasus eksekusi normal dan pembatalan pengguna. Inilah sebabnya mengapa properti RunWorkerCompletedEventArgs.Cancelled ada. Pada dasarnya, melakukan ini dengan benar mengharuskan Anda menganggap metode Batal Anda sebagai metode asinkron itu sendiri.Ini sebuah contoh:
Jika Anda benar-benar tidak ingin metode Anda keluar, saya sarankan meletakkan bendera seperti
AutoResetEvent
pada turunanBackgroundWorker
, kemudian menimpaOnRunWorkerCompleted
untuk mengatur bendera. Ini masih agak kludgy; Saya akan merekomendasikan memperlakukan acara batal seperti metode asinkron dan melakukan apa pun yang sedang dilakukan diRunWorkerCompleted
handler.sumber
Saya agak terlambat ke pesta di sini (sekitar 4 tahun) tetapi bagaimana dengan menyiapkan utas sinkron yang dapat menangani loop sibuk tanpa mengunci UI, kemudian meminta panggilan balik dari utas itu menjadi konfirmasi bahwa BackgroundWorker telah selesai membatalkan ?
Sesuatu seperti ini:
Intinya yang dilakukan adalah menjalankan utas lain untuk dijalankan di latar belakang yang hanya menunggu di loop sibuk untuk melihat apakah
MyWorker
telah selesai. SetelahMyWorker
selesai membatalkan utas akan keluar dan kita dapat menggunakannyaAsyncCallback
untuk menjalankan metode apa pun yang kita butuhkan untuk mengikuti pembatalan yang berhasil - itu akan berfungsi seperti acara psuedo. Karena ini terpisah dari utas UI, ia tidak akan mengunci UI sementara kami menunggu untukMyWorker
menyelesaikan pembatalan. Jika niat Anda adalah mengunci dan menunggu pembatalan maka ini tidak berguna bagi Anda, tetapi jika Anda hanya ingin menunggu sehingga Anda dapat memulai proses lain maka ini berfungsi dengan baik.sumber
Saya tahu ini benar-benar terlambat (5 tahun) tetapi yang Anda cari adalah menggunakan Thread dan SynchronizationContext . Anda harus membuat panggilan UI kembali ke utas UI "dengan tangan" daripada membiarkan Framework melakukannya secara otomatis.
Ini memungkinkan Anda menggunakan Thread yang bisa Anda Tunggu jika perlu.
sumber
sumber
Solusi Fredrik Kalseth untuk masalah ini adalah yang terbaik yang saya temukan sejauh ini. Penggunaan solusi lain
Application.DoEvent()
yang dapat menyebabkan masalah atau tidak berfungsi. Biarkan saya memberikan solusinya ke kelas yang dapat digunakan kembali. KarenaBackgroundWorker
tidak disegel, kita dapat mengambil kelas kita darinya:Dengan bendera dan penguncian yang tepat, kami memastikan bahwa
_resetEvent.WaitOne()
hanya dipanggil jika beberapa pekerjaan telah dimulai, jika tidak_resetEvent.Set();
mungkin tidak akan pernah dipanggil!_resetEvent.Set();
Try- akhirnya memastikan bahwa akan dipanggil, bahkan jika pengecualian harus terjadi di DoWork-handler kami. Kalau tidak, aplikasi bisa membeku selamanya saat meneleponCancelSync
!Kami akan menggunakannya seperti ini:
Anda juga dapat menambahkan penangan ke
RunWorkerCompleted
acara seperti yang ditunjukkan di sini:BackgroundWorker Class (dokumentasi Microsoft) .
sumber
Menutup formulir menutup logfile terbuka saya. Pekerja latar belakang saya menulis file log itu, jadi saya tidak bisa membiarkannya
MainWin_FormClosing()
selesai sampai pekerja latar belakang saya berakhir. Jika saya tidak menunggu pekerja latar belakang saya untuk berhenti, pengecualian terjadi.Mengapa ini sangat sulit?
Sebuah
Thread.Sleep(1500)
karya sederhana , tetapi menunda shutdown (jika terlalu lama), atau menyebabkan pengecualian (jika terlalu pendek).Untuk mematikan tepat setelah pekerja latar belakang berakhir, cukup gunakan variabel. Ini bekerja untuk saya:
sumber
Anda dapat menggagalkan acara RunWorkerCompleted. Bahkan jika Anda sudah menambahkan event handler untuk _worker, Anda bisa menambahkan yang lain yang akan mereka jalankan sesuai urutan penambahannya.
ini bisa bermanfaat jika Anda memiliki beberapa alasan mengapa pembatalan dapat terjadi, membuat logika satu penangan RunWorkerCompleted lebih rumit dari yang Anda inginkan. Misalnya, membatalkan ketika pengguna mencoba menutup formulir:
sumber
Saya menggunakan
async
metode danawait
untuk menunggu pekerja menyelesaikan tugasnya:dan dalam
DoWork
metode:Anda juga dapat merangkum
while
loop dalamDoWork
dengantry ... catch
untuk mengatur_isBusy
adalahfalse
pengecualian. Atau, cukup periksa_worker.IsBusy
diStopAsync
loop while.Berikut ini adalah contoh implementasi penuh:
Untuk menghentikan pekerja dan menunggu hingga akhir:
Masalah dengan metode ini adalah:
sumber
oh man, beberapa di antaranya menjadi sangat rumit. yang perlu Anda lakukan adalah memeriksa properti BackgroundWorker.CancellationPending di dalam handler DoWork. Anda dapat memeriksanya kapan saja. setelah tertunda, atur e.Cancel = True dan bail dari metode.
// metode di sini private void Worker_DoWork (pengirim objek, DoWorkEventArgs e) {BackgroundWorker bw = (pengirim sebagai BackgroundWorker);
}
sumber