186

過去数日間、私は.net 4.5とc#5の新機能をテストしました。

私はその新しい非同期/待機機能が好きです。以前、 BackgroundWorkerを使用して、レスポンシブUIを使用してバックグラウンドでより長いプロセスを処理していました。

私の質問は、これらの素晴らしい新機能を使用した後、いつasync / awaitを使用する必要があり、いつBackgroundWorkerを使用する必要があるのか​​ということです。両方に共通するシナリオはどれですか?

4

5 に答える 5

229

This is likely TL;DR for many, but, I think comparing await with BackgroundWorker is like comparing apples and oranges and my thoughts on this follow:

BackgroundWorker is meant to model a single task that you'd want to perform in the background, on a thread pool thread. async/await is a syntax for asynchronously awaiting on asynchronous operations. Those operations may or may not use a thread pool thread or even use any other thread. So, they're apples and oranges.

For example, you can do something like the following with await:

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

But, you'd likely never model that in a background worker, you'd likely do something like this in .NET 4.0 (prior to 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);

Notice the disjointness of the disposal compared between the two syntaxes and how you can't use using without async/await.

But, you wouldn't do something like that with BackgroundWorker. BackgroundWorker is usually for modeling a single long-running operation that you don't want to impact the UI responsiveness. For example:

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

There's really nothing there you can use async/await with, BackgroundWorker is creating the thread for you.

Now, you could use TPL instead:

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

In which case the TaskScheduler is creating the thread for you (assuming the default TaskScheduler), and could use await as follows:

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"

In my opinion, a major comparison is whether you're reporting progress or not. For example, you might have a BackgroundWorker like this:

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

But, you wouldn't deal with some of this because you'd drag-and-drop the background worker component on to the design surface of a form--something you can't do with async/await and Task... i.e. you won't manually create the object, set the properties and set the event handlers. you'd only fill in the body of the DoWork, RunWorkerCompleted, and ProgressChanged event handlers.

If you "converted" that to async/await, you'd do something like:

     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"

Without the ability to drag a component on to a Designer surface, it's really up to the reader to decide which is "better". But, that, to me, is the comparison between await and BackgroundWorker, not whether you can await built-in methods like Stream.ReadAsync. e.g. if you were using BackgroundWorker as intended, it could be hard to convert to use await.

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

于 2012-09-13T22:29:41.563 に答える
79

async/await は、 などの構造を置き換えるように設計されていBackgroundWorkerます。必要に応じて使用することもできますが、async/await を他のいくつかの TPL ツールと共に使用して、そこにあるすべてのものを処理できるはずです。

どちらも機能するため、いつどちらを使用するかは個人の好みに依存します。あなたにとって何が速いですか?何が分かりやすいです

于 2012-09-13T20:55:11.517 に答える
23

これは良い紹介です: http://msdn.microsoft.com/en-us/library/hh191443.aspx スレッド セクションはまさにあなたが探しているものです:

非同期メソッドは、非ブロッキング操作を目的としています。非同期メソッドの await 式は、待機中のタスクの実行中に現在のスレッドをブロックしません。代わりに、式はメソッドの残りの部分を継続として登録し、非同期メソッドの呼び出し元に制御を返します。

async および await キーワードによって、追加のスレッドが作成されることはありません。非同期メソッドは独自のスレッドで実行されないため、非同期メソッドはマルチスレッドを必要としません。メソッドは現在の同期コンテキストで実行され、メソッドがアクティブな場合にのみスレッドで時間を使用します。Task.Run を使用して、CPU バウンドの作業をバックグラウンド スレッドに移動できますが、バックグラウンド スレッドは、結果が利用可能になるのを待っているだけのプロセスには役立ちません。

非同期プログラミングに対する非同期ベースのアプローチは、ほとんどの場合、既存のアプローチよりも優れています。特に、このアプローチは IO バウンド操作の場合、BackgroundWorker よりも優れています。これは、コードが単純であり、競合状態を防ぐ必要がないためです。Task.Run と組み合わせると、非同期プログラミングは、コード実行の調整の詳細を、Task.Run がスレッドプールに転送する作業から分離するため、CPU バウンド操作の場合は BackgroundWorker よりも優れています。

于 2012-09-13T21:30:15.353 に答える
11

BackgroundWorkerは、.NET 4.5 では廃止されたと明示的にラベル付けされています。

MSDN の記事「Async と Await を使用した非同期プログラミング (C# および Visual Basic)」では、次のように説明されています。

非同期プログラミングへの非同期ベースのアプローチは、ほとんどの場合、既存のアプローチよりも優れています。特に、このアプローチはIO バウンド操作の場合はBackgroundWorker よりも優れて います。これは、コードが単純で、競合状態を防ぐ必要がないためです。Task.Run と組み合わせると、非同期プログラミングは、コード実行の調整の詳細を、Task.Runがスレッドプールに転送する作業から分離するため、CPU バウンド操作ではBackgroundWorkerよりも優れています。

アップデート

  • @eran-otzapのコメントへの応答:
    「コードが単純で、競合状態を防ぐ必要がないため、IO バウンド操作の場合」どのような競合状態が発生する可能性がありますか?例を挙げていただけますか? "

この質問は別の投稿として配置する必要がありました。

ウィキペディアには、レースの条件に関する適切な説明があります。その必要な部分はマルチスレッドであり、同じMSDNの記事からAsync and Await (C# and Visual Basic) を使用した非同期プログラミング:

非同期メソッドは、非ブロッキング操作を目的としています。非同期メソッドの await 式は、待機中のタスクの実行中に現在のスレッドをブロックしません。代わりに、式はメソッドの残りの部分を継続として登録し、非同期メソッドの呼び出し元に制御を返します。

async および await キーワードによって、追加のスレッドが作成されることはありません。非同期メソッドは独自のスレッドで実行されないため、非同期メソッドはマルチスレッドを必要としません。メソッドは現在の同期コンテキストで実行され、メソッドがアクティブな場合にのみスレッドで時間を使用します。Task.Run を使用して、CPU バウンドの作業をバックグラウンド スレッドに移動できますが、バックグラウンド スレッドは、結果が利用可能になるのを待っているだけのプロセスには役立ちません。

非同期プログラミングに対する非同期ベースのアプローチは、ほとんどの場合、既存のアプローチよりも優れています。特に、このアプローチは IO バウンド操作の場合、BackgroundWorker よりも優れています。これは、コードが単純であり、競合状態を防ぐ必要がないためです。Task.Run と組み合わせると、非同期プログラミングは、コード実行の調整の詳細を、Task.Run がスレッドプールに転送する作業から分離するため、CPU バウンド操作では BackgroundWorker よりも優れています。

つまり、「async および await キーワードによって追加のスレッドが作成されることはありません」。

1 年前にこの記事を研究していたときの私自身の試みを思い出す限り、同じ記事のコード サンプルを実行して遊んだことがある場合、その非非同期バージョン (変換しようとすることができます)無期限ブロック!

また、具体的な例については、このサイトを検索できます。以下にいくつかの例を示します。

于 2013-05-03T19:43:31.660 に答える