Async/ждет от BackgroundWorker

В последние несколько дней я тестировал новые функции .net 4.5 и С# 5.

Мне нравятся его новые функции async/await. Раньше я использовал BackgroundWorker для обработки более длинных процессов в фоновом режиме с помощью адаптивного пользовательского интерфейса.

Мой вопрос: после появления этих приятных новых функций, когда следует использовать async/wait и когда BackgroundWorker? Каковы общие сценарии для обоих?

Ответ 1

async/await предназначен для замены конструкций, таких как BackgroundWorker. Хотя вы, безусловно, можете использовать его, если хотите, вы должны иметь возможность использовать async/wait, а также несколько других инструментов TPL, чтобы обрабатывать все, что там есть.

Поскольку оба работают, это сводится к личным предпочтениям, которые вы используете, когда. Что для вас быстрее? Что вам легче понять?

Ответ 2

Скорее всего, 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);
    }
}

Но вы, вероятно, никогда не будете моделировать это в фоновом рабочем столе, вы, вероятно, сделаете что-то подобное в .NET 4.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);

Обратите внимание на несовместимость удалений по сравнению с двумя синтаксисами и то, как вы не можете использовать using без async/await.

Но вы бы не сделали что-то подобное с BackgroundWorker. BackgroundWorker обычно используется для моделирования одной продолжительной операции, в которой вы не хотите влиять на отзывчивость пользовательского интерфейса. Например:

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

Но вы не имеете дело с некоторыми из этого, потому что вы перетаскиваете компонент рабочего фона на конструктивную поверхность формы - то, что вы не можете сделать с помощью async/await и Task... т.е. вы не будете вручную создавать объект, задавать свойства и устанавливать обработчики событий. вы должны заполнить только тело обработчиков событий DoWork, RunWorkerCompleted и ProgressChanged.

Если вы "конвертировали" это в async/await, вы бы сделали что-то вроде:

     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

Ответ 3

Это хорошее введение: http://msdn.microsoft.com/en-us/library/hh191443.aspx Раздел "Темы" - это то, что вы ищете:

Асинхронные методы предназначены для неблокирующих операций. Выражение ожидания в методе async не блокирует текущий поток во время ожидания ожидаемой задачи. Вместо этого выражение подписывает остальную часть метода как продолжение и возвращает управление вызывающей стороне метода async.

Асинхронные и ожидающие ключевые слова не создают дополнительные потоки. Асинхронные методы не требуют многопоточности, потому что метод async не запускается в своем потоке. Метод работает в текущем контексте синхронизации и использует время в потоке только тогда, когда метод активен. Вы можете использовать Task.Run для перемещения работы, связанной с процессором, в фоновый поток, но фоновый поток не помогает процессу, который просто ждет, когда результаты станут доступными.

Асинхронный подход к асинхронному программированию предпочтительнее существующих подходов практически в каждом случае. В частности, этот подход лучше, чем BackgroundWorker для операций с привязкой к IO, потому что код проще, и вам не нужно защищать от условий гонки. В сочетании с Task.Run асинхронное программирование лучше, чем BackgroundWorker для операций с привязкой к процессору, потому что асинхронное программирование разделяет координационные данные о запуске вашего кода из работы, которую Task.Run передает в threadpool.

Ответ 4

BackgroundWorker явно помечен как устаревший в .NET 4.5:

Статья MSDN "Асинхронное программирование с помощью Async и Await (С# и Visual Basic)" сообщает:

Асинхронный подход к асинхронному программированию является предпочтительным к существующим подходам почти в каждом случае. В частности, это подход лучше, чем BackgroundWorker для операций с привязкой к IOпотому что код проще, и вам не нужно защищать от гонки условия. В сочетании с Task.Run лучше работает асинхронное программирование чем BackgroundWorker для операций с привязкой к ЦП, поскольку async программирование разделяет координационные данные о запуске вашего кода из работы, которая Task.Run переносится в threadpool

UPDATE

  • в ответ на @eran-otzap комментарий:
    "для операций с привязкой к IO, потому что код проще, и вам не нужно защищать условия гонки". Какие условия гонки могут возникнуть, можете ли вы привести пример? "

Этот вопрос должен был быть помещен как отдельный пост.

В Википедии есть хорошее объяснение условий racing. Необходимая его часть - многопоточность и одна и та же статья MSDN Асинхронное программирование с использованием Async и Await (С# и Visual Basic):

Асинхронные методы предназначены для неблокирующих операций. Ожидание выражение в асинхронном методе не блокирует текущий поток while ожидаемая задача запущена. Вместо этого выражение подписывает остальное метода в качестве продолжения и возвращает управление вызывающей стороне асинхронный метод.

Асинхронные и ожидающие ключевые слова не вызывают дополнительных потоков создано. Асинхронные методы не требуют многопоточности, поскольку асинхронный метод не запускается в своем потоке. Метод работает на текущем синхронизации и использует время в потоке только тогда, когда метод активен. Вы можете использовать Task.Run, чтобы переместить работу, связанную с процессором, на фоновый поток, но фоновый поток не помогает с процессом что просто ждет, когда результаты станут доступными.

Асинхронный подход к асинхронному программированию предпочтительнее существующих подходов практически в каждом случае. В частности, этот подход лучше, чем BackgroundWorker для операций с привязкой к IO, потому что код проще, и вам не нужно защищать от условий гонки. В сочетании с Task.Run асинхронное программирование лучше, чем BackgroundWorker для операций с привязкой к процессору, потому что асинхронное программирование разделяет координационные данные о выполнении вашего кода из работы что Task.Run передает в threadpool

То есть, "Асинхронные и ожидающие ключевые слова не создают дополнительные потоки".

Насколько я могу вспомнить свои собственные попытки, когда я изучал эту статью год назад, если вы запустили и сыграли с образцом кода из той же статьи, вы можете столкнуться с ситуацией, когда ее неасинхронные версии (вы могли бы попробуйте преобразовать его в себя) блокируйте бесконечно!

Кроме того, для конкретных примеров вы можете выполнить поиск на этом сайте. Вот несколько примеров: