Async / menunggu vs BackgroundWorker

162

Dalam beberapa hari terakhir saya telah menguji fitur baru .net 4.5 dan c # 5.

Saya suka fitur async / menunggu baru. Sebelumnya saya telah menggunakan BackgroundWorker untuk menangani proses yang lebih lama di latar belakang dengan UI responsif.

Pertanyaan saya adalah: setelah memiliki fitur-fitur baru yang bagus ini, kapan saya harus menggunakan async / tunggu dan kapan BackgroundWorker ? Yang merupakan skenario umum untuk keduanya?

Tom
sumber
Keduanya baik, tetapi jika Anda bekerja dengan kode lama yang belum dimigrasi ke versi .net yang lebih baru; BackgroundWorker bekerja pada keduanya.
dcarl661

Jawaban:

74

async / await dirancang untuk menggantikan konstruksi seperti BackgroundWorker. Meskipun Anda pasti bisa menggunakannya jika Anda mau, Anda harus dapat menggunakan async / menunggu, bersama dengan beberapa alat TPL lainnya, untuk menangani semua yang ada di luar sana.

Karena keduanya berfungsi, itu tergantung pada preferensi pribadi yang Anda gunakan saat. Apa yang lebih cepat untukmu ? Apa yang lebih mudah bagi Anda untuk mengerti?

Melayani
sumber
17
Terima kasih. Bagi saya async / menunggu tampaknya jauh lebih jelas dan 'alami'. BakcgoundWorker membuat kode 'berisik' menurut saya.
Tom
12
@ Tom Nah, itu sebabnya Microsoft menghabiskan banyak waktu dan upaya untuk mengimplementasikannya. Jika tidak ada yang lebih baik, mereka tidak akan terganggu
Servy
5
Iya. Hal-hal yang menunggu baru membuat BackgroundWorker lama tampak benar-benar lebih rendah dan usang. Perbedaannya sangat dramatis.
usr
16
Saya memiliki ikhtisar yang cukup bagus di blog saya membandingkan berbagai pendekatan untuk tugas latar belakang. Perhatikan bahwa async/ awaitjuga memungkinkan pemrograman asinkron tanpa thread pool threads.
Stephen Cleary
8
Meremehkan jawaban ini, ini menyesatkan. Async / menunggu TIDAK dirancang untuk menggantikan pekerja latar belakang.
Quango
206

Ini mungkin TL; DR bagi banyak orang, tetapi, saya pikir membandingkan awaitdenganBackgroundWorker seperti membandingkan apel dan jeruk dan pemikiran saya tentang hal berikut:

BackgroundWorkerdimaksudkan untuk memodelkan satu tugas yang ingin Anda lakukan di latar belakang, pada utas kumpulan utas. async/ awaitadalah sintaks untuk menunggu secara tidak sinkron pada operasi asinkron. Operasi-operasi itu mungkin atau mungkin tidak menggunakan thread pool thread atau bahkan menggunakan utas lainnya . Jadi, mereka adalah apel dan jeruk.

Misalnya, Anda dapat melakukan sesuatu seperti berikut ini dengan await:

using (WebResponse response = await webReq.GetResponseAsync())
{
    using (Stream responseStream = response.GetResponseStream())
    {
        int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length);
    }
}

Tapi, Anda mungkin tidak akan pernah memodelkan hal itu di latar belakang pekerja, Anda mungkin akan melakukan sesuatu seperti ini di .NET 4.0 (sebelum await):

webReq.BeginGetResponse(ar =>
{
    WebResponse response = webReq.EndGetResponse(ar);
    Stream responseStream = response.GetResponseStream();
    responseStream.BeginRead(buffer, 0, buffer.Length, ar2 =>
    {
        int bytesRead = responseStream.EndRead(ar2);
        responseStream.Dispose();
        ((IDisposable) response).Dispose();
    }, null);
}, null);

Perhatikan perbedaan pemisahan antara kedua sintaks dan bagaimana Anda tidak dapat menggunakan usingtanpa async/ await.

Tetapi, Anda tidak akan melakukan hal seperti itu dengan BackgroundWorker. BackgroundWorkerbiasanya untuk memodelkan operasi jangka panjang tunggal yang Anda tidak ingin memengaruhi respons UI. Sebagai contoh:

worker.DoWork += (sender, e) =>
                    {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                    };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // TODO: do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

Benar-benar tidak ada di sana Anda dapat menggunakan async / tunggu, BackgroundWorkermembuat utas untuk Anda.

Sekarang, Anda bisa menggunakan TPL sebagai gantinya:

var synchronizationContext = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
                      {
                        int i = 0;
                        // simulate lengthy operation
                        Stopwatch sw = Stopwatch.StartNew();
                        while (sw.Elapsed.TotalSeconds < 1)
                            ++i;
                      }).ContinueWith(t=>
                                      {
                                        // TODO: do something on the UI thread, like
                                        // update status or display "result"
                                      }, synchronizationContext);

Dalam hal TaskSchedulerini membuat utas untuk Anda (dengan asumsi default TaskScheduler), dan dapat digunakan awaitsebagai berikut:

await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

Menurut pendapat saya, perbandingan utama adalah apakah Anda melaporkan kemajuan atau tidak. Misalnya, Anda mungkin memiliki BackgroundWorker likeini:

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.ProgressChanged += (sender, eventArgs) =>
                            {
                            // TODO: something with progress, like update progress bar

                            };
worker.DoWork += (sender, e) =>
                 {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                            ((BackgroundWorker)sender).ReportProgress((int) (1000 / sw.ElapsedMilliseconds));
                        ++i;
                    }
                 };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

Tapi, Anda tidak akan berurusan dengan beberapa hal ini karena Anda akan menyeret-dan-menjatuhkan komponen pekerja latar belakang ke permukaan desain formulir - sesuatu yang tidak dapat Anda lakukan dengan async/ awaitdan Task... yaitu Anda menang ' t secara manual membuat objek, mengatur properti dan mengatur event handler. Anda hanya akan mengisi tubuh DoWork, RunWorkerCompleteddanProgressChanged event handler.

Jika "dikonversi" menjadi async / menunggu, Anda akan melakukan sesuatu seperti:

     IProgress<int> progress = new Progress<int>();

     progress.ProgressChanged += ( s, e ) =>
        {
           // TODO: do something with e.ProgressPercentage
           // like update progress bar
        };

     await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                        {
                            progress.Report((int) (1000 / sw.ElapsedMilliseconds))
                        }
                        ++i;
                    }
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

Tanpa kemampuan untuk menyeret komponen ke permukaan Designer, terserah pembaca untuk memutuskan mana yang "lebih baik". Tapi, bagi saya, itu adalah perbandingan antara awaitdan BackgroundWorker, bukan apakah Anda bisa menunggu metode bawaan seperti Stream.ReadAsync. mis. jika Anda menggunakan BackgroundWorkersebagaimana dimaksud, mungkin sulit untuk mengkonversi untuk digunakanawait .

Pikiran lain: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html

Peter Ritchie
sumber
2
Satu kekurangan yang saya pikir ada dengan async / tunggu adalah bahwa Anda mungkin ingin memulai beberapa tugas async sekaligus. menunggu dimaksudkan untuk menunggu setiap tugas selesai sebelum memulai yang berikutnya. Dan jika Anda menghilangkan kata kunci yang menunggu maka metode berjalan secara sinkron yang bukan yang Anda inginkan. Saya tidak berpikir async / menunggu dapat menyelesaikan masalah seperti "mulai 5 tugas ini dan telepon saya kembali ketika setiap tugas dilakukan tanpa urutan tertentu".
Trevor Elliott
4
@Moozhe. Tidak benar, Anda bisa melakukannya var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. Yang akan menunggu dua operasi paralel. Menunggu jauh lebih baik untuk tugas yang tidak sinkron, tetapi berurutan, IMO ...
Peter Ritchie
2
@ Moozhe ya, melakukannya dengan cara itu mempertahankan urutan tertentu - seperti yang saya sebutkan. ini adalah titik utama dari menunggu adalah untuk mendapatkan asynchronisity dalam kode yang terlihat berurutan. Anda bisa, tentu saja, gunakan await Task.WhenAny(t1, t2)untuk melakukan sesuatu ketika salah satu tugas selesai terlebih dahulu. Anda mungkin ingin perulangan untuk memastikan tugas lainnya selesai juga. Biasanya Anda ingin tahu kapan tugas tertentu selesai, yang mengarahkan Anda untuk menulis urutan await.
Peter Ritchie
2
Bukankah. NET 4.0 TPL yang membuat pola asinkron APM, EAP dan BackgroundWorker usang? Masih bingung tentang hal itu
Gennady Vanin Геннадий Ванин
5
Kejujuran, BackgroundWorker tidak pernah baik untuk operasi yang terikat IO.
Peter Ritchie
21

Ini adalah pengantar yang bagus: http://msdn.microsoft.com/en-us/library/hh191443.aspx Bagian Threads adalah apa yang Anda cari:

Metode Async dimaksudkan sebagai operasi non-pemblokiran. Ekspresi menunggu dalam metode async tidak memblokir utas saat ini sementara tugas yang ditunggu-tunggu sedang berjalan. Sebaliknya, ekspresi mendaftar sisa metode sebagai kelanjutan dan mengembalikan kontrol ke pemanggil metode async.

Async dan menunggu kata kunci tidak menyebabkan utas tambahan dibuat. Metode Async tidak memerlukan multithreading karena metode async tidak berjalan pada utasnya sendiri. Metode ini berjalan pada konteks sinkronisasi saat ini dan menggunakan waktu pada utas hanya ketika metode ini aktif. Anda dapat menggunakan Task.Run untuk memindahkan pekerjaan yang terikat CPU ke utas latar belakang, tetapi utas latar tidak membantu proses yang hanya menunggu hasil tersedia.

Pendekatan berbasis async untuk pemrograman asinkron lebih disukai daripada pendekatan yang ada di hampir setiap kasus. Secara khusus, pendekatan ini lebih baik daripada BackgroundWorker untuk operasi yang terikat IO karena kodenya lebih sederhana dan Anda tidak harus waspada terhadap kondisi balapan. Dalam kombinasi dengan Task.Run, pemrograman async lebih baik daripada BackgroundWorker untuk operasi yang terikat CPU karena pemrograman async memisahkan rincian koordinasi menjalankan kode Anda dari pekerjaan yang ditransfer Task.Run ke threadpool.

TommyN
sumber
"untuk operasi yang terikat IO karena kodenya lebih sederhana dan Anda tidak harus waspada terhadap kondisi lomba" Kondisi lomba apa yang dapat terjadi, dapatkah Anda memberi contoh?
eran otzap
8

BackgroundWorker secara eksplisit dilabeli sebagai usang di .NET 4.5:

Artikel MSDN "Pemrograman Asinkron dengan Async dan Menunggu (C # dan Visual Basic)" memberi tahu:

Pendekatan berbasis async untuk pemrograman asinkron lebih disukai daripada pendekatan yang ada di hampir setiap kasus . Secara khusus, pendekatan ini lebih baik daripada BackgroundWorker untuk operasi yang terikat IO karena kodenya lebih sederhana dan Anda tidak harus waspada terhadap kondisi balapan. Dalam kombinasi dengan Task.Run, pemrograman async lebih baik daripada BackgroundWorker untuk operasi yang terikat CPU karena pemrograman async memisahkan rincian koordinasi menjalankan kode Anda dari pekerjaan yang ditransfer oleh Task.Run ke threadpool

MEMPERBARUI

  • sebagai tanggapan terhadap komentar @ eran-otzap :
    "untuk operasi yang terikat IO karena kodenya lebih sederhana dan Anda tidak harus waspada terhadap kondisi lomba" Kondisi lomba apa yang dapat terjadi, dapatkah Anda memberikan contoh? "

Pertanyaan ini seharusnya ditempatkan sebagai pos terpisah.

Wikipedia memiliki penjelasan yang bagus tentang kondisi balap . Bagian yang diperlukan adalah multithreading dan dari artikel MSDN yang sama Pemrograman Asinkron dengan Async dan Await (C # dan Visual Basic) :

Metode Async dimaksudkan sebagai operasi non-pemblokiran. Ekspresi menunggu dalam metode async tidak memblokir utas saat ini sementara tugas yang ditunggu-tunggu sedang berjalan. Sebaliknya, ekspresi mendaftar sisa metode sebagai kelanjutan dan mengembalikan kontrol ke pemanggil metode async.

Async dan menunggu kata kunci tidak menyebabkan utas tambahan dibuat. Metode Async tidak memerlukan multithreading karena metode async tidak berjalan pada utasnya sendiri. Metode ini berjalan pada konteks sinkronisasi saat ini dan menggunakan waktu pada utas hanya ketika metode ini aktif. Anda dapat menggunakan Task.Run untuk memindahkan pekerjaan yang terikat CPU ke utas latar belakang, tetapi utas latar tidak membantu proses yang hanya menunggu hasil tersedia.

Pendekatan berbasis async untuk pemrograman asinkron lebih disukai daripada pendekatan yang ada di hampir setiap kasus. Secara khusus, pendekatan ini lebih baik daripada BackgroundWorker untuk operasi yang terikat IO karena kodenya lebih sederhana dan Anda tidak harus waspada terhadap kondisi balapan. Dalam kombinasi dengan Task.Run, pemrograman async lebih baik daripada BackgroundWorker untuk operasi yang terikat CPU karena pemrograman async memisahkan rincian koordinasi menjalankan kode Anda dari pekerjaan yang ditransfer Task.Run ke threadpool

Yaitu, "Kata kunci async dan menunggu tidak menyebabkan utas tambahan dibuat".

Sejauh yang saya ingat upaya saya ketika saya mempelajari artikel ini setahun yang lalu, jika Anda telah menjalankan dan bermain dengan sampel kode dari artikel yang sama, Anda dapat bertemu dalam situasi bahwa itu versi non-async (Anda dapat mencoba untuk mengkonversi untuk dirimu sendiri) blokir tanpa batas!

Juga, untuk contoh nyata Anda dapat mencari situs ini. Berikut ini beberapa contohnya:

Gennady Vanin Геннадий Ванин
sumber
30
BackgrondWorker tidak secara eksplisit dilabeli sebagai usang di .NET 4.5. Artikel MSDN hanya mengatakan bahwa operasi yang terikat IO lebih baik dengan metode async - penggunaan BackgroundWorker tidak berarti Anda tidak dapat menggunakan metode async.
Peter Ritchie
@PeterRitchie, saya mengoreksi jawaban saya. Bagi saya, "pendekatan yang ada sudah usang" adalah sinonim dengan "Pendekatan berbasis async untuk pemrograman asinkron lebih disukai daripada pendekatan yang ada di hampir setiap kasus"
Gennady Vanin Геннадий Ванин
7
Saya mengambil masalah dengan halaman MSDN itu. Pertama, Anda tidak lagi melakukan "koordinasi" dengan BGW dibandingkan dengan Tugas. Dan, ya BGW tidak pernah dimaksudkan untuk secara langsung melakukan opersi IO - selalu ada cara yang lebih baik untuk melakukan IO daripada di BGW. Jawaban lain menunjukkan BGW tidak lebih kompleks untuk digunakan daripada Tugas. Dan jika Anda menggunakan BGW dengan benar, tidak ada kondisi balapan.
Peter Ritchie
"untuk operasi yang terikat IO karena kodenya lebih sederhana dan Anda tidak harus waspada terhadap kondisi lomba" Kondisi lomba apa yang dapat terjadi, dapatkah Anda memberi contoh?
eran otzap
11
Jawaban ini salah. Pemrograman asinkron juga dapat dengan mudah memicu kebuntuan dalam program non-sepele. Sebagai perbandingan BackgroundWorker sederhana dan kokoh.
ZunTzu