В последние несколько дней я тестировал новые функции .net 4.5 и С# 5.
Мой вопрос: после появления этих приятных новых функций, когда следует использовать async/wait и когда BackgroundWorker? Каковы общие сценарии для обоих?
Ответ 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
Ответ 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
То есть, "Асинхронные и ожидающие ключевые слова не создают дополнительные потоки".
Насколько я могу вспомнить свои собственные попытки, когда я изучал эту статью год назад, если вы запустили и сыграли с образцом кода из той же статьи, вы можете столкнуться с ситуацией, когда ее неасинхронные версии (вы могли бы попробуйте преобразовать его в себя) блокируйте бесконечно!
Кроме того, для конкретных примеров вы можете выполнить поиск на этом сайте. Вот несколько примеров: