Асинхронізація / очікування проти BackgroundWorker


162

За останні кілька днів я перевірив нові можливості .net 4.5 та c # 5.

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

Моє запитання: після отримання цих нових приємних функцій, коли я повинен використовувати функцію async / wait і коли BackgroundWorker ? Які спільні сценарії для обох?



І те й інше добре, але якщо ви працюєте зі старшим кодом, який не був перенесений на більш пізню версію .net; BackgroundWorker працює на обох.
dcarl661

Відповіді:


74

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

Оскільки обидві працюють, це зводиться до особистих уподобань щодо того, яким ви користуєтесь коли. Що швидше для вас ? Що легше для вас , щоб зрозуміти?


17
Дякую. Для мене асинхронізація / очікування здається набагато більш зрозумілою та «природною». BakcgoundWorker робить на мій погляд код «галасливим».
Том

12
@Tom Ну, ось чому Microsoft витратила багато часу та зусиль на її реалізацію. Якби не краще, вони б не заважали
Servy

5
Так. Новий матеріал, який очікує, робить старий BackgroundWorker абсолютно неповноцінним і застарілим. Різниця в тому, що драматична.
usr

16
У моєму щоденнику я досить непоганий, порівнюючи різні підходи до фонових завдань. Зауважте, що async/ awaitтакож дозволяє асинхронне програмування без потоків пулу потоків.
Стівен Клірі

8
Відмовляючись від цієї відповіді, вона вводить в оману. Async / wait не призначений для заміни фонового працівника.
Quango

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 / ждати, 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обробники подій.

Якщо ви "перетворили" це на асинхронізацію / очікування, ви зробите щось на кшталт:

     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 / wait, полягає в тому, що ви можете запустити кілька завдань асинхронізації одночасно. "Очікувати" означає зачекати виконання кожного завдання перед початком наступного. І якщо ви пропустите ключове слово очікування, то метод працює синхронно, що не є тим, що ви хочете. Я не думаю, що асинхронізація / очікування може вирішити таку проблему, як "запустити ці 5 завдань і передзвоніть мені, коли кожне завдання буде виконано в конкретному порядку".
Тревор Елліотт

4
@Moozhe. Неправда, можна зробити var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. Що чекало б двох паралельних операцій. Чекайте набагато краще для асинхронних, але послідовних завдань, ІМО ...
Пітер Річі

2
@Moozhe так, роблячи це таким чином, підтримує певну послідовність - як я вже згадував. це головний момент очікування - отримати асинхронність у послідовно виглядаючому коді. Звичайно, ви можете використовувати await Task.WhenAny(t1, t2)щось для того , щоб виконати будь-яке завдання першим. Ви, ймовірно, хочете, щоб цикл переконався, що і інше завдання теж виконане. Зазвичай ви хочете знати, коли конкретне завдання виконується, що призводить вас до написання послідовних awaits.
Пітер Річі


5
Чесність, BackgroundWorker ніколи не був хорошим для операцій, пов'язаних з IO.
Пітер Річі

21

Це гарне вступ: http://msdn.microsoft.com/en-us/library/hh191443.aspx Розділ "Нитки" - це саме те, що ви шукаєте:

Методи асинхронізації призначені для неблокуючих операцій. Вираз очікування в методі асинхронізації не блокує поточний потік під час виконання очікуваного завдання. Натомість вираз підписує решту методу у вигляді продовження та повертає керування виклику методу async.

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

Асинхронний підхід до асинхронного програмування є переважним для існуючих підходів майже в кожному випадку. Зокрема, цей підхід кращий, ніж BackgroundWorker для операцій, пов’язаних з IO, оскільки код простіший і вам не потрібно захищати від перегонових умов. У поєднанні з Task.Run, програмування з асинхронією краще, ніж BackgroundWorker для пов'язаних з процесором операцій, оскільки програмування async відокремлює координаційні деталі виконання вашого коду від роботи, яку Task.Run переносить у нитку.


"для операцій, пов'язаних з IO, тому що код простіший і вам не потрібно захищатись від перегонових умов". Які умови гонки можуть виникнути, ви можете навести приклад?
eran otzap

8

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

Стаття MSDN "Асинхронне програмування за допомогою Async і Await (C # і Visual Basic)" розповідає:

Асинхронний підхід до асинхронного програмування є переважним для існуючих підходів майже в кожному випадку . Зокрема, цей підхід кращий, ніж BackgroundWorker для операцій, пов’язаних з IO, оскільки код простіший і вам не потрібно захищати від перегонових умов. У поєднанні з Task.Run, програмування з асинхронією краще, ніж BackgroundWorker для операцій, пов'язаних з процесором, оскільки програмування async відокремлює координаційні деталі виконання вашого коду від роботи, яку Task.Run переносить у нитку

ОНОВЛЕННЯ

  • у відповідь на коментар @ eran-otzap :
    "для операцій, пов'язаних з IO, тому що код простіший і вам не потрібно захищатись від перегонових умов". Які умови гонки можуть виникнути, ви можете навести приклад? "

Це питання слід було поставити як окрему посаду.

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

Методи асинхронізації призначені для неблокуючих операцій. Вираз очікування в методі асинхронізації не блокує поточний потік під час виконання очікуваного завдання. Натомість вираз підписує решту методу у вигляді продовження та повертає керування виклику методу async.

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

Асинхронний підхід до асинхронного програмування є переважним для існуючих підходів майже в кожному випадку. Зокрема, цей підхід кращий, ніж BackgroundWorker для операцій, пов’язаних з IO, оскільки код простіший і вам не потрібно захищати від перегонових умов. У поєднанні з Task.Run програмування асинхроніки краще, ніж BackgroundWorker для пов'язаних з процесором операцій, оскільки програмування async відокремлює координаційні деталі виконання вашого коду від роботи, яку Task.Run переносить у нитку

Тобто, "Ключові слова асинхронізації та очікування не викликають створення додаткових потоків".

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

Також для конкретних прикладів ви можете шукати цей сайт. Ось кілька прикладів:


30
BackgrondWorker явно не позначений застарілим у .NET 4.5. У статті MSDN просто сказано, що операції, пов'язані з IO, краще з методами асинхронізації - використання BackgroundWorker не означає, що ви не можете використовувати методи async.
Пітер Річі

@ PeterRitchie, я виправив свою відповідь. Для мене "існуючі підходи застаріли" є синонімом "Асинхронний підхід до асинхронного програмування є переважним, ніж існуючий підхід майже в кожному випадку"
Геннадій Ванін Геннадій Ванін

7
Я приймаю проблему з цією сторінкою MSDN. Для одного, ви не здійснюєте більше "координації" з BGW, ніж у Task. Та, так, BGW ніколи не мав на меті безпосередньо виконувати операції з виводу IO - завжди був кращий спосіб зробити IO, ніж у BGW. Інша відповідь показує, що BGW не є більш складним у використанні, ніж Завдання. І якщо ви правильно користуєтеся BGW, гоночних умов немає.
Пітер Річі

"для операцій, пов'язаних з IO, тому що код простіший і вам не потрібно захищатись від перегонових умов". Які умови гонки можуть виникнути, ви можете навести приклад?
eran otzap

11
Ця відповідь неправильна. Асинхронне програмування може надто легко запускати тупики в нетривіальних програмах. У порівнянні BackgroundWorker простий і твердий.
ZunTzu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.