非同期/待機とBackgroundWorker


162

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

新しい非同期/待機機能が気に入っています。以前、BackgroundWorkerを使用して、応答性の高いUIでバックグラウンドでより長いプロセスを処理していました。

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



どちらも問題ありませんが、新しい.netバージョンに移行されていない古いコードを使用している場合は、BackgroundWorkerは両方で機能します。
dcarl661

回答:


74

async / awaitは、などの構成を置き換えるように設計されていBackgroundWorkerます。あなたは確かにいる間ことができ、それを使用するあなたがしたい場合は、そこだすべてを処理するために、いくつかの他のTPLツールと一緒に、非同期/のawaitを使用することができるはずです。

どちらも機能するので、いつどのように使用するかは個人の好みに帰着します。あなたにとって何がより速いですか?あなたが理解しやすいのは何ですか?


17
ありがとう。私にとって、非同期/待機ははるかに明確で「自然」に見えます。私の考えでは、BakcgoundWorkerはコードを「うるさい」ようにします。
トム

12
@トムまあ、それがマイクロソフトがそれを実装するために多くの時間と努力を費やした理由です。それがもっと良くなかったら、彼らは気にならなかったでしょう
サービー2012

5
はい。新しいawaitのものは、古いBackgroundWorkerを完全に劣っており、時代遅れに見えるようにします。違いはその劇的です。
usr

16
私のブログでは、バックグラウンドタスクへのさまざまなアプローチを比較たかなり良い概要があります。async/ awaitは、スレッドプールスレッドなしの非同期プログラミングも許可することに注意してください。
スティーブンクリアリー2012

8
この回答に反対票を投じた、それは誤解を招くものです。Async / awaitは、バックグラウンドワーカーを置き換えるようには設計されていません。
Quango、2014年

206

これはおそらく多くの場合TL; DRですが、比較awaitすることBackgroundWorkerはリンゴとオレンジを比較するようなものだと思います。

BackgroundWorkerは、バックグラウンドでスレッドプールスレッドに対して実行する単一のタスクをモデル化するためのものです。 async/ awaitは、非同期操作で非同期に待機するための構文です。これらの操作は、スレッドプールスレッドを使用する場合と使用しない場合があります。つまり、リンゴとオレンジです。

たとえば、次のようなことができますawait

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

ただし、バックグラウンドワーカーでモデル化することはおそらくないでしょう。.NET4.0では、次のようなことをするでしょう(以前は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);

2つの構文間で比較された破棄のばらばらさと、/ usingなしでは使用できないことに注意してください。asyncawait

しかし、そのようなことをで行うことはありませんBackgroundWorkerBackgroundWorker通常は、UIの応答性に影響を与えたくない単一の長時間実行オペレーションをモデル化するためのものです。例えば:

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

async / awaitで使用できるものは何もなくBackgroundWorker、スレッドを作成しています。

これで、代わりにTPLを使用できます。

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

この場合、TaskSchedulerはスレッドを作成し(デフォルトはTaskScheduler)、await次のように使用できます。

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"

私の意見では、主要な比較は、進捗状況を報告しているかどうかです。たとえば、BackgroundWorker likeこれがあるかもしれません:

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

あなたが行うことができない何かを-しかし、あなたはドラッグ&ドロップ形式の設計面へのバックグラウンドワーカーコンポーネントをしたいので、この一部に対処しませんasync/ awaitTaskあなたが「勝った...つまり、 tオブジェクトを手動で作成し、プロパティを設定し、イベントハンドラーを設定します。あなただけのボディを埋めるだろうDoWorkRunWorkerCompletedと、ProgressChangedイベントハンドラ。

それを非同期/待機に「変換」した場合は、次のようにします。

     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"

コンポーネントをデザイナーサーフェイスにドラッグする機能がない場合、どちらが「より良い」かを判断するのはリーダー次第です。しかし、それは私にとって、との比較でawaitありBackgroundWorker、のような組み込みメソッドを待つことができるかどうかではありませんStream.ReadAsync。たとえば、BackgroundWorker意図したとおりに使用している場合は、に変換するのが難しい場合がありますawait

その他の考え:http : //jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html


2
async / awaitに存在する1つの欠点は、複数の非同期タスクを一度に開始したい場合があることです。awaitは、各タスクが完了するのを待ってから次のタスクを開始することを意味します。また、awaitキーワードを省略した場合、メソッドは同期的に実行されますが、これは望ましいことではありません。async / awaitが「これらの5つのタスクを開始し、各タスクが特定の順序で実行されないときにコールバックする」などの問題を解決できるとは思いません。
Trevor Elliott

4
@Moozhe。真実ではない、あなたはできるvar t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;。2つの並列操作を待ちます。Awaitは非同期ではるかに優れていますが、シーケンシャルタスク、IMO ...
Peter Ritchie

2
@Moozheはい、そのように実行すると、特定のシーケンスが維持されます-前述のとおり。これが待機の主なポイントは、シーケンシャルなコードで非同期性を取得することです。もちろん、await Task.WhenAny(t1, t2)どちらかのタスクが最初に完了したときに何かを行うために使用できます。他のタスクも確実に完了するためのループが必要になる可能性があります。通常は、特定のタスクがいつ完了するかを知りたいため、シーケンシャルを記述しawaitます。
Peter Ritchie


5
正直なところ、BackgroundWorkerはIOにバインドされた操作には決して適していません
Peter Ritchie

21

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

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

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

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


「コードがよりシンプルで、競合状態から保護する必要がないため、IOにバインドされた操作の場合」どのような競合状態が発生する可能性がありますか、例を挙げていただけますか?
eran otzap 2013

8

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

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

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

更新

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

この質問は別の投稿として投稿されるべきでした。

ウィキペディアには、レース状況についての良い説明があります。その必要な部分はマルチスレッド化であり、同じMSDNの記事から非同期と待機を使用した非同期プログラミング(C#およびVisual Basic)からです。

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

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

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

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

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

また、具体的な例については、このサイトを検索できます。ここにいくつかの例があります:


30
BackgrondWorkerは、.NET 4.5では廃止されたと明示的にラベル付けされていません。MSDNの記事では、非同期メソッドの方がIOバインド操作の方が優れていると述べています。BackgroundWorkerを使用しても、非同期メソッドを使用できないわけではありません。
Peter Ritchie

@PeterRitchie、私は私の答えを修正しました。私にとって、「既存のアプローチは廃止された」とは、「非同期プログラミングへの非同期ベースのアプローチは、ほとんどすべての場合、既存のアプローチよりも好ましい」と同義語です
Gennady VaninennaеннадийВанинMay

7
そのMSDNページに問題があります。まず、BGWで "調整"を行うのは、タスクで行う場合と同じです。そして、はい、BGWは、IO操作を直接実行することを意図したものでは決してありませんでした。BGW よりも常に IOを実行するための優れた方法でした。もう1つの答えは、BGWはTaskよりも使用が複雑ではないことを示しています。また、BGWを正しく使用すれば、競合状態は発生しません。
Peter Ritchie 2013年

「コードがよりシンプルで、競合状態から保護する必要がないため、IOにバインドされた操作の場合」どのような競合状態が発生する可能性がありますか、例を挙げていただけますか?
eran otzap 2013

11
この答えは間違っています。非同期プログラミングは、重要なプログラムでデッドロックを簡単に引き起こします。比較すると、BackgroundWorkerはシンプルで堅実です。
ZunTzu、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.