Async / ожидание против BackgroundWorker

162

В последние несколько дней я тестировал новые возможности .net 4.5 и c # 5.

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

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

Том
источник
Оба хороши, но если вы работаете со старым кодом, который не был перенесен в более позднюю версию .net; BackgroundWorker работает на обоих.
dcarl661

Ответы:

74

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

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

Servy
источник
17
Спасибо. Для меня async / await кажется гораздо более понятным и «естественным». BakcgoundWorker делает код «шумным», на мой взгляд.
Том
12
@ Tom Ну, вот почему Microsoft потратила много времени и сил на его внедрение. Если бы не было ничего лучше, они бы не потрудились
Servy
5
Да. Новое ожидание делает старый BackgroundWorker совершенно не нужным и устаревшим. Разница настолько драматична.
USR
16
У меня есть довольно хорошее краткое изложение в моем блоге, сравнивающее различные подходы к фоновым задачам. Обратите внимание, что async/ awaitтакже позволяет асинхронное программирование без потоков пула потоков.
Стивен Клири
8
Проголосовал этот ответ, он вводит в заблуждение. Async / await НЕ предназначен для замены фонового рабочего.
Куанго
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);
    }
}

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

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... т.е. вы выиграли » t создать объект вручную, установить свойства и установить обработчики событий. Вы бы заполнить только в теле 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

Питер Ричи
источник
2
Я думаю, что в async / await существует один недостаток: вы можете запускать несколько асинхронных задач одновременно. await означает ожидание завершения каждой задачи перед началом следующей. И если вы опустите ключевое слово await, то метод будет работать синхронно, что не то, что вам нужно. Я не думаю, что async / await может решить такую ​​проблему, как «запустить эти 5 задач и перезвонить мне, когда каждая задача выполняется в определенном порядке».
Тревор Эллиотт
4
@Moozhe. Не правда, вы можете сделать var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. Который будет ждать две параллельные операции. Ожидать намного лучше для асинхронных, но последовательных задач, ИМО ...
Питер Ричи
2
@ Може да, при таком способе поддерживается определенная последовательность - как я уже говорил. это главное, чего стоит ожидать - это получить асинхронность в последовательном коде. Конечно, вы можете использовать await Task.WhenAny(t1, t2)что-нибудь, когда любая из задач будет выполнена первой. Вы, вероятно, захотите цикл, чтобы убедиться, что другая задача завершена тоже. Обычно вы хотите знать, когда конкретная задача завершается, что приводит вас к написанию последовательных awaits.
Питер Ричи
2
Разве это не было .NET 4.0 TPL, из-за которого асинхронные шаблоны APM, EAP и BackgroundWorker устарели? Все еще смущен по этому поводу
Геннадий Ванин Геннадий Ванин
5
Честно говоря, BackgroundWorker никогда не был хорош для операций, связанных с IO.
Питер Ричи
21

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

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

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

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

TommyN
источник
«для операций, связанных с вводом-выводом, потому что код проще и вам не нужно защищаться от условий гонки» Какие условия могут возникнуть, не могли бы вы привести пример?
Эран Отзап 28.09.13
8

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

Статья MSDN "Асинхронное программирование с использованием Async и Await (C # и Visual Basic)" рассказывает:

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

ОБНОВИТЬ

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

Этот вопрос нужно было поставить отдельным постом.

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

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

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

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

То есть «ключевые слова async и await не вызывают создание дополнительных потоков».

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

Также по конкретным примерам можно поискать на этом сайте. Вот несколько примеров:

Геннадий Ванин Геннадий Ванин
источник
30
BackgrondWorker явно не помечен как устаревший в .NET 4.5. В статье MSDN просто говорится, что операции, связанные с IO, лучше с асинхронными методами - использование BackgroundWorker не означает, что вы не можете использовать асинхронные методы.
Питер Ричи
@PeterRitchie, я исправил свой ответ. Для меня «существующие подходы устарели» - это синоним «Асинхронный подход к асинхронному программированию предпочтительнее существующих подходов почти во всех случаях»,
Геннадий Ванин Геннадий Ванин
7
Я не согласен с этой страницей MSDN. С одной стороны, вы не более «координируете» с BGW, чем с Task. И да, BGW никогда не предназначался для непосредственного выполнения операций ввода-вывода - всегда был лучший способ сделать ввод-вывод, чем в BGW. Другой ответ показывает, что BGW не сложнее в использовании, чем Task. И если вы правильно используете BGW, никаких условий гонки нет.
Питер Ричи
«для операций, связанных с вводом-выводом, потому что код проще и вам не нужно защищаться от условий гонки» Какие условия могут возникнуть, не могли бы вы привести пример?
Эран Отзап 28.09.13
11
Этот ответ неверен. Асинхронное программирование может слишком легко вызывать взаимные блокировки в нетривиальных программах. В сравнении BackgroundWorker прост и надежен.
ZunTzu