Async / await vs BackgroundWorker


162

Trong vài ngày qua, tôi đã thử nghiệm các tính năng mới của .net 4.5 và c # 5.

Tôi thích tính năng async / await mới của nó. Trước đó tôi đã sử dụng BackgroundWorker để xử lý các quá trình dài hơn trong nền với giao diện người dùng đáp ứng.

Câu hỏi của tôi là: sau khi có các tính năng mới tuyệt vời này, khi nào tôi nên sử dụng async / await và khi BackgroundWorker ? Đó là những kịch bản phổ biến cho cả hai?



Cả hai đều tốt, nhưng nếu bạn làm việc với mã cũ chưa được chuyển sang phiên bản .net mới hơn; BackgroundWorker hoạt động trên cả hai.
dcarl661

Câu trả lời:


74

async / await được thiết kế để thay thế các cấu trúc như BackgroundWorker. Mặc dù bạn chắc chắn có thể sử dụng nó nếu bạn muốn, bạn sẽ có thể sử dụng async / await, cùng với một vài công cụ TPL khác, để xử lý mọi thứ ngoài kia.

Vì cả hai đều hoạt động, nó tùy thuộc vào sở thích cá nhân mà bạn sử dụng khi nào. Điều gì nhanh hơn cho bạn ? Điều gì dễ hiểu hơn cho bạn ?


17
Cảm ơn. Đối với tôi, async / await dường như rõ ràng và 'tự nhiên' hơn nhiều. BakcgoundWorker làm cho mã 'ồn ào' theo ý kiến ​​của tôi.
Tom

12
@Tom Vâng, đó là lý do tại sao Microsoft đã dành rất nhiều thời gian và nỗ lực để thực hiện nó. Nếu không tốt hơn họ sẽ không làm phiền
Phục vụ

5
Đúng. Các công cụ chờ đợi mới làm cho BackgroundWorker cũ dường như hoàn toàn thấp kém và lỗi thời. Sự khác biệt là kịch tính.
usr

16
Tôi đã có một danh sách khá tốt trên blog của mình khi so sánh các cách tiếp cận khác nhau đối với các tác vụ nền. Lưu ý rằng async/ awaitcũng cho phép lập trình không đồng bộ mà không có chủ đề nhóm luồng.
Stephen Cleary

8
Đánh giá thấp câu trả lời này, nó gây hiểu nhầm. Async / await KHÔNG được thiết kế để thay thế công nhân nền.
Quango

206

Đây có thể là TL; DR đối với nhiều người, nhưng, tôi nghĩ so sánh awaitvới BackgroundWorkergiống như so sánh táo và cam và suy nghĩ của tôi về điều này như sau:

BackgroundWorkerđược dùng để mô hình hóa một tác vụ duy nhất mà bạn muốn thực hiện trong nền, trên một chuỗi nhóm luồng. async/ awaitlà một cú pháp để chờ không đồng bộ trên các hoạt động không đồng bộ. Những hoạt động đó có thể hoặc không thể sử dụng một chuỗi nhóm luồng hoặc thậm chí sử dụng bất kỳ luồng nào khác . Vì vậy, chúng là táo và cam.

Ví dụ: bạn có thể làm một cái gì đó như sau với await:

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

Nhưng, bạn có thể không bao giờ mô hình hóa rằng trong một nhân viên nền, bạn có thể sẽ làm một cái gì đó như thế này trong .NET 4.0 (trước đó 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);

Lưu ý sự khác biệt của việc xử lý được so sánh giữa hai cú pháp và cách bạn không thể sử dụng usingmà không có async/ await.

Nhưng, bạn sẽ không làm điều gì đó như thế với BackgroundWorker. BackgroundWorkerthường là để mô hình hóa một hoạt động dài hạn mà bạn không muốn tác động đến khả năng đáp ứng của UI. Ví dụ:

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();

Thực sự không có gì ở đó bạn có thể sử dụng async / await với, BackgroundWorkerđang tạo chủ đề cho bạn.

Bây giờ, bạn có thể sử dụng TPL thay thế:

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);

Trong trường hợp đó, TaskSchedulerviệc tạo chủ đề cho bạn (giả sử là mặc định TaskScheduler) và có thể sử dụng awaitnhư sau:

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"

Theo tôi, một so sánh chính là liệu bạn có báo cáo tiến độ hay không. Ví dụ: bạn có thể có một BackgroundWorker likecái này:

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();

Tuy nhiên, bạn sẽ không giải quyết một số vấn đề này vì bạn kéo và thả thành phần nhân viên nền trên bề mặt thiết kế của biểu mẫu - điều bạn không thể làm với async/ awaitTask... tức là bạn đã thắng ' Tạo thủ công đối tượng, đặt thuộc tính và đặt trình xử lý sự kiện. bạn chỉ muốn điền thông tin vào cơ thể của DoWork, RunWorkerCompletedProgressChangedxử lý sự kiện.

Nếu bạn "chuyển đổi" thành async / await, bạn sẽ làm một cái gì đó như:

     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"

Không có khả năng kéo một thành phần lên bề mặt Nhà thiết kế, người đọc thực sự quyết định cái nào là "tốt hơn". Nhưng, điều đó, với tôi, là sự so sánh giữa awaitBackgroundWorker, không phải là liệu bạn có thể chờ đợi các phương thức tích hợp như thế nào không Stream.ReadAsync. ví dụ: nếu bạn đang sử dụng BackgroundWorkernhư dự định, có thể khó chuyển đổi sang sử dụng await.

Những suy nghĩ khác: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html


2
Một lỗ hổng tôi nghĩ tồn tại với async / await là bạn có thể muốn bắt đầu nhiều tác vụ async cùng một lúc. Chờ đợi có nghĩa là chờ đợi cho mỗi nhiệm vụ hoàn thành trước khi bắt đầu nhiệm vụ tiếp theo. Và nếu bạn bỏ qua từ khóa đang chờ thì phương thức sẽ chạy đồng bộ không phải là điều bạn muốn. Tôi không nghĩ async / await có thể giải quyết vấn đề như "bắt đầu 5 nhiệm vụ này và gọi lại cho tôi khi mỗi nhiệm vụ được thực hiện không theo thứ tự cụ thể".
Trevor Elliott

4
@Moozhe. Không đúng, bạn có thể làm var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. Mà sẽ chờ hai hoạt động song song. Chờ đợi sẽ tốt hơn nhiều cho các tác vụ không đồng bộ, nhưng tuần tự, IMO ...
Peter Ritchie

2
@Moozhe có, làm theo cách đó duy trì một trình tự nhất định - như tôi đã đề cập. đây là điểm chính của sự chờ đợi là để có được sự không đồng bộ trong mã tìm kiếm tuần tự. Tất nhiên, bạn có thể sử dụng await Task.WhenAny(t1, t2)để làm một cái gì đó khi một trong hai nhiệm vụ hoàn thành trước. Bạn có thể muốn một vòng lặp để đảm bảo nhiệm vụ khác cũng hoàn thành. Thông thường bạn muốn biết khi nào một nhiệm vụ cụ thể hoàn thành, điều này dẫn bạn đến việc viết awaits liên tiếp .
Peter Ritchie


5
Thành thật mà nói, BackgroundWorker chưa bao giờ tốt cho các hoạt động ràng buộc IO.
Peter Ritchie

21

Đây là một giới thiệu tốt: http://msdn.microsoft.com/en-us/l Library / hh191443.aspx Phần Chủ đề chính là thứ bạn đang tìm kiếm:

Các phương thức Async được dự định là các hoạt động không chặn. Biểu thức chờ trong phương thức async không chặn luồng hiện tại trong khi tác vụ được chờ đang chạy. Thay vào đó, biểu thức đăng ký phần còn lại của phương thức dưới dạng tiếp tục và trả lại quyền điều khiển cho người gọi phương thức async.

Các từ khóa không đồng bộ và chờ đợi không gây ra các chủ đề bổ sung được tạo. Các phương thức async không yêu cầu đa luồng vì một phương thức async không chạy trên luồng của chính nó. Phương thức chạy trên bối cảnh đồng bộ hóa hiện tại và chỉ sử dụng thời gian trên luồng khi phương thức được kích hoạt. Bạn có thể sử dụng Task.Run để di chuyển công việc được liên kết với CPU sang luồng nền, nhưng luồng nền không giúp ích gì cho quá trình chỉ chờ kết quả khả dụng.

Cách tiếp cận dựa trên async để lập trình không đồng bộ thích hợp hơn các cách tiếp cận hiện có trong hầu hết mọi trường hợp. Đặc biệt, cách tiếp cận này tốt hơn BackgroundWorker cho các hoạt động ràng buộc IO vì mã đơn giản hơn và bạn không phải bảo vệ chống lại các điều kiện chủng tộc. Kết hợp với Task.Run, lập trình async tốt hơn BackgroundWorker cho các hoạt động gắn với CPU vì lập trình async tách biệt các chi tiết phối hợp để chạy mã của bạn khỏi công việc mà Task.Run chuyển sang luồng xử lý.


"cho các hoạt động ràng buộc IO vì mã đơn giản hơn và bạn không phải bảo vệ chống lại các điều kiện chủng tộc" Điều kiện chủng tộc nào có thể xảy ra, bạn có thể đưa ra một ví dụ không?
eran otzap

8

BackgroundWorker được dán nhãn rõ ràng là lỗi thời trong .NET 4.5:

Bài viết MSDN "Lập trình không đồng bộ với Async và Await (C # và Visual Basic)" cho biết:

Cách tiếp cận dựa trên async để lập trình không đồng bộ thích hợp hơn các cách tiếp cận hiện có trong hầu hết mọi trường hợp . Đặc biệt, cách tiếp cận này tốt hơn BackgroundWorker cho các hoạt động ràng buộc IO vì mã đơn giản hơn và bạn không phải bảo vệ chống lại các điều kiện chủng tộc. Kết hợp với Task.Run, lập trình async tốt hơn BackgroundWorker cho các hoạt động gắn với CPU vì lập trình async tách biệt các chi tiết phối hợp để chạy mã của bạn khỏi công việc mà Task.Run chuyển sang luồng xử lý

CẬP NHẬT

  • để đáp lại nhận xét của @ eran-otzap :
    "cho các hoạt động ràng buộc IO vì mã đơn giản hơn và bạn không phải bảo vệ chống lại các điều kiện chủng tộc" Điều kiện chủng tộc nào có thể xảy ra, bạn có thể đưa ra một ví dụ không? "

Câu hỏi này nên được đặt như một bài riêng biệt.

Wikipedia có một lời giải thích tốt về điều kiện đua xe . Phần cần thiết của nó là đa luồng và từ cùng một bài viết MSDN Lập trình không đồng bộ với Async và Await (C # và Visual Basic) :

Các phương thức Async được dự định là các hoạt động không chặn. Biểu thức chờ trong phương thức async không chặn luồng hiện tại trong khi tác vụ được chờ đang chạy. Thay vào đó, biểu thức đăng ký phần còn lại của phương thức dưới dạng tiếp tục và trả lại quyền điều khiển cho người gọi phương thức async.

Các từ khóa không đồng bộ và chờ đợi không gây ra các chủ đề bổ sung được tạo. Các phương thức async không yêu cầu đa luồng vì một phương thức async không chạy trên luồng của chính nó. Phương thức chạy trên bối cảnh đồng bộ hóa hiện tại và chỉ sử dụng thời gian trên luồng khi phương thức được kích hoạt. Bạn có thể sử dụng Task.Run để di chuyển công việc được liên kết với CPU sang luồng nền, nhưng luồng nền không giúp ích gì cho quá trình chỉ chờ kết quả khả dụng.

Cách tiếp cận dựa trên async để lập trình không đồng bộ thích hợp hơn các cách tiếp cận hiện có trong hầu hết mọi trường hợp. Đặc biệt, cách tiếp cận này tốt hơn BackgroundWorker cho các hoạt động ràng buộc IO vì mã đơn giản hơn và bạn không phải bảo vệ chống lại các điều kiện chủng tộc. Kết hợp với Task.Run, lập trình async tốt hơn BackgroundWorker cho các hoạt động gắn với CPU vì lập trình async tách biệt các chi tiết phối hợp để chạy mã của bạn khỏi công việc mà Task.Run chuyển sang luồng xử lý

Đó là, "Không đồng bộ và chờ từ khóa không gây ra các chủ đề bổ sung được tạo".

Theo như tôi có thể nhớ lại những nỗ lực của chính mình khi tôi đang nghiên cứu bài viết này một năm trước, nếu bạn đã chạy và chơi với mẫu mã từ cùng một bài viết, bạn có thể gặp phải các phiên bản không đồng bộ của nó (bạn có thể thử chuyển đổi nó cho chính mình) chặn vô thời hạn!

Ngoài ra, cho các ví dụ cụ thể bạn có thể tìm kiếm trang web này. Dưới đây là một số ví dụ:


30
BackgrondWorker không được dán nhãn rõ ràng là lỗi thời trong .NET 4.5. Bài viết MSDN chỉ nói rằng các hoạt động ràng buộc IO tốt hơn với các phương thức async - sử dụng BackgroundWorker không có nghĩa là bạn không thể sử dụng các phương thức async.
Peter Ritchie

@PeterRitchie, tôi đã sửa câu trả lời của mình. Đối với tôi, "phương pháp tiếp cận hiện là lỗi thời" là từ đồng nghĩa với "Phương pháp async dựa trên lập trình không đồng bộ là thích hợp hơn để tiếp cận hiện có trong hầu hết các trường hợp"
Gennady Vanin Геннадий Ванин

7
Tôi có vấn đề với trang MSDN đó. Đối với một người, bạn không thực hiện "phối hợp" với BGW nhiều hơn bạn làm với Nhiệm vụ. Và, vâng, BGW không bao giờ có ý định trực tiếp thực hiện các hoạt động IO - luôn có cách tốt hơn để thực hiện IO so với BGW. Câu trả lời khác cho thấy BGW không phức tạp hơn để sử dụng so với Nhiệm vụ. Và nếu bạn sử dụng BGW chính xác, không có điều kiện cuộc đua.
Peter Ritchie

"cho các hoạt động ràng buộc IO vì mã đơn giản hơn và bạn không phải bảo vệ chống lại các điều kiện chủng tộc" Điều kiện chủng tộc nào có thể xảy ra, bạn có thể đưa ra một ví dụ không?
eran otzap

11
Câu trả lời này là sai. Lập trình không đồng bộ có thể quá dễ dàng gây ra các bế tắc trong các chương trình không tầm thường. Trong so sánh BackgroundWorker là đơn giản và đá rắn.
ZunTzu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.