To prawdopodobnie TL; DR dla wielu, ale myślę, że porównywanie await
z porównaniem BackgroundWorker
jest jak porównywanie jabłek i pomarańczy, a moje przemyślenia na ten temat są następujące:
BackgroundWorker
ma na celu modelowanie pojedynczego zadania, które chcesz wykonać w tle, w wątku puli wątków. async
/ await
to składnia asynchronicznego oczekiwania na operacje asynchroniczne. Te operacje mogą, ale nie muszą, używać wątku puli wątków lub nawet używać dowolnego innego wątku . Więc to jabłka i pomarańcze.
Na przykład możesz wykonać następujące czynności await
:
using (WebResponse response = await webReq.GetResponseAsync())
{
using (Stream responseStream = response.GetResponseStream())
{
int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length);
}
}
Ale prawdopodobnie nigdy nie modelowałbyś tego w pracy w tle, prawdopodobnie zrobiłbyś coś takiego w .NET 4.0 (przed 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);
Zwróć uwagę na rozłączność rozdysponowania w porównaniu między dwiema składniami i tym, jak nie możesz użyć using
bez async
/ await
.
Ale nie zrobiłbyś czegoś takiego z BackgroundWorker
. BackgroundWorker
służy zwykle do modelowania pojedynczej długotrwałej operacji, na którą nie chcesz mieć wpływu na responsywność interfejsu użytkownika. Na przykład:
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();
Naprawdę nie ma tam nic, z czym możesz użyć async / await, BackgroundWorker
tworzy wątek dla Ciebie.
Teraz możesz zamiast tego użyć 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);
W takim przypadku TaskScheduler
tworzy wątek dla Ciebie (zakładając domyślny TaskScheduler
) i może użyć await
w następujący sposób:
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"
Moim zdaniem główne porównanie dotyczy tego, czy zgłaszasz postępy, czy nie. Na przykład możesz mieć BackgroundWorker like
to:
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();
Ale nie poradziłbyś sobie z niektórymi z tego, ponieważ przeciągnąłbyś i upuść komponent roboczy w tle na powierzchnię projektu formularza - coś, czego nie możesz zrobić z async
/ await
i Task
... tj. Wygrałeś ” Utwórz obiekt ręcznie, ustaw właściwości i ustaw programy obsługi zdarzeń. chcesz wypełnić tylko w organizmie z DoWork
, RunWorkerCompleted
i ProgressChanged
obsługi zdarzeń.
Jeśli „przekonwertowałeś” to na async / await, zrobiłbyś coś takiego:
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"
Bez możliwości przeciągania komponentu na powierzchnię Projektanta, tak naprawdę to czytelnik decyduje, co jest „lepsze”. Ale to jest dla mnie porównanie między await
a BackgroundWorker
, a nie tym, czy można czekać na wbudowane metody, takie jak Stream.ReadAsync
. np. jeśli używasz BackgroundWorker
zgodnie z przeznaczeniem, konwersja do użycia może być trudna await
.
Inne przemyślenia: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html