Async / await vs BackgroundWorker

162

En los últimos días he probado las nuevas características de .net 4.5 y c # 5.

Me gustan sus nuevas funciones async / wait. Anteriormente había usado BackgroundWorker para manejar procesos más largos en segundo plano con una interfaz de usuario receptiva.

Mi pregunta es: después de tener estas nuevas características agradables, ¿cuándo debo usar async / await y cuándo un BackgroundWorker ? ¿Cuáles son los escenarios comunes para ambos?

Tom
fuente
Ambos son buenos, pero si está trabajando con código anterior que no se ha migrado a una versión posterior de .net; BackgroundWorker trabaja en ambos.
dcarl661

Respuestas:

74

async / await está diseñado para reemplazar construcciones como BackgroundWorker. Si bien ciertamente puede usarlo si lo desea, debería poder usar async / wait, junto con algunas otras herramientas TPL, para manejar todo lo que existe.

Dado que ambos funcionan, todo se reduce a la preferencia personal de cuál usar cuando. ¿Qué es más rápido para ti ? Lo que es más fácil para que usted pueda entender?

Servy
fuente
17
Gracias. Para mí, async / waitit parece mucho más claro y 'natural'. BakcgoundWorker hace que el código sea 'ruidoso' en mi opinión.
Tom
12
@Tom Bueno, por eso Microsoft dedicó mucho tiempo y esfuerzo a implementarlo. Si no hubiera sido mejor, no se habrían molestado
Servy
55
Si. El nuevo material de espera hace que el viejo BackgroundWorker parezca totalmente inferior y obsoleto. La diferencia es tan dramática.
usr
16
Tengo un resumen bastante bueno en mi blog comparando diferentes enfoques para las tareas en segundo plano. Tenga en cuenta que async/ awaittambién permite la programación asincrónica sin hilos de grupo de subprocesos.
Stephen Cleary
8
Voto rechazado esta respuesta, es engañoso. Async / await NO está diseñado para reemplazar al trabajador en segundo plano.
Quango
206

Esto es probable TL; DR para muchos, pero creo que comparar await con BackgroundWorkeres como comparar manzanas y naranjas y mis pensamientos sobre esto son los siguientes:

BackgroundWorkerestá destinado a modelar una única tarea que desea realizar en segundo plano, en un subproceso de grupo de subprocesos. async/ awaites una sintaxis para esperar asincrónicamente operaciones asincrónicas. Esas operaciones pueden o no usar un subproceso de grupo de subprocesos o incluso usar cualquier otro subproceso . Entonces, son manzanas y naranjas.

Por ejemplo, puede hacer algo como lo siguiente con await:

using (WebResponse response = await webReq.GetResponseAsync())
{
    using (Stream responseStream = response.GetResponseStream())
    {
        int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length);
    }
}

Pero, probablemente nunca modelarías eso en un trabajador en segundo plano, probablemente harías algo como esto en .NET 4.0 (antes 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);

Observe la desunión de la eliminación en comparación entre las dos sintaxis y cómo no puede usar usingsinasync / await.

Pero, no harías algo así con eso BackgroundWorker. BackgroundWorkerPor lo general, es para modelar una sola operación de larga duración que no desea afectar la capacidad de respuesta de la interfaz de usuario. Por ejemplo:

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

Realmente no hay nada allí con lo que pueda usar async / wait, BackgroundWorkerestá creando el hilo para usted.

Ahora, podría usar TPL en su lugar:

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

En cuyo caso, TaskSchedulerestá creando el hilo para usted (asumiendo el valor predeterminado TaskScheduler), y podría usar awaitlo siguiente:

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"

En mi opinión, una comparación importante es si estás informando progreso o no. Por ejemplo, puede tener BackgroundWorker likeesto:

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

Pero no se ocuparía de algo de esto porque arrastraría y soltaría el componente de trabajador de fondo en la superficie de diseño de un formulario, algo que no puede hacer con async/ awaity Task... es decir, ganó ' t cree manualmente el objeto, establezca las propiedades y configure los controladores de eventos. solo llenarías el cuerpo de la DoWork,RunWorkerCompleted y ProgressChangedlos controladores de eventos.

Si "convertiste" eso en asíncrono / espera, harías algo como:

     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"

Sin la capacidad de arrastrar un componente a una superficie de diseñador, realmente depende del lector decidir cuál es "mejor". Pero, para mí, esa es la comparación entre awaity BackgroundWorkerno si puedes esperar métodos incorporados como Stream.ReadAsync. por ejemplo, si estaba usando BackgroundWorkersegún lo previsto, podría ser difícil de convertir para usar await.

Otros pensamientos: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html

Peter Ritchie
fuente
2
Una falla que creo que existe con async / await es que es posible que desee iniciar varias tareas asincrónicas a la vez. esperar debe esperar a que se complete cada tarea antes de comenzar la siguiente. Y si omite la palabra clave await, el método se ejecuta sincrónicamente, lo que no es lo que desea. No creo que async / await pueda resolver un problema como "iniciar estas 5 tareas y devolverme la llamada cuando cada tarea se realice sin un orden en particular".
Trevor Elliott
44
@Moozhe. No es cierto, puedes hacerlo var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. Lo que esperaría dos operaciones paralelas. Aguardar es mucho mejor para tareas asincrónicas, pero secuenciales, OMI ...
Peter Ritchie
2
@Moozhe sí, hacerlo de esa manera mantiene una cierta secuencia, como mencioné. Este es el principal punto de espera es obtener la asincronización en el código de aspecto secuencial. Por supuesto, podría await Task.WhenAny(t1, t2)hacer algo cuando la tarea se completara primero. Es probable que desee un bucle para asegurarse de que la otra tarea también se complete. Por lo general, desea saber cuándo se completa una tarea específica , lo que lo lleva a escribir awaits secuenciales .
Peter Ritchie
2
¿No fue .NET 4.0 TPL lo que hizo que los patrones asíncronos APM, EAP y BackgroundWorker fueran obsoletos? Todavía confundido al respecto
Gennady Vanin Геннадий Ванин
55
La honestidad, BackgroundWorker nunca fue bueno para las operaciones vinculadas a IO.
Peter Ritchie
21

Esta es una buena introducción: http://msdn.microsoft.com/en-us/library/hh191443.aspx La sección Hilos es justo lo que está buscando:

Los métodos asíncronos están destinados a ser operaciones sin bloqueo. Una expresión de espera en un método asíncrono no bloquea el hilo actual mientras se ejecuta la tarea esperada. En cambio, la expresión registra el resto del método como una continuación y devuelve el control al llamador del método asíncrono.

Las palabras clave asíncronas y en espera no hacen que se creen hilos adicionales. Los métodos asincrónicos no requieren subprocesos múltiples porque un método asincrónico no se ejecuta en su propio hilo. El método se ejecuta en el contexto de sincronización actual y usa el tiempo en el hilo solo cuando el método está activo. Puede usar Task.Run para mover el trabajo vinculado a la CPU a un subproceso en segundo plano, pero un subproceso en segundo plano no ayuda con un proceso que solo está esperando que los resultados estén disponibles.

El enfoque asincrónico de la programación asincrónica es preferible a los enfoques existentes en casi todos los casos. En particular, este enfoque es mejor que BackgroundWorker para operaciones vinculadas a IO porque el código es más simple y no tiene que protegerse contra las condiciones de carrera. En combinación con Task.Run, la programación asincrónica es mejor que BackgroundWorker para operaciones vinculadas a la CPU porque la programación asincrónica separa los detalles de coordinación de ejecutar su código del trabajo que Task.Run transfiere al conjunto de hilos.

TommyN
fuente
"para operaciones vinculadas a IO porque el código es más simple y no tiene que protegerse contra las condiciones de carrera" ¿Qué condiciones de carrera pueden ocurrir, podría dar un ejemplo?
eran otzap
8

BackgroundWorker está explícitamente etiquetado como obsoleto en .NET 4.5:

El artículo de MSDN "Programación asincrónica con asíncrono y espera (C # y Visual Basic)" dice:

El enfoque asincrónico de la programación asincrónica es preferible a los enfoques existentes en casi todos los casos . En particular, este enfoque es mejor que BackgroundWorker para operaciones vinculadas a IO porque el código es más simple y no tiene que protegerse contra las condiciones de carrera. En combinación con Task.Run, la programación asincrónica es mejor que BackgroundWorker para operaciones vinculadas a la CPU porque la programación asincrónica separa los detalles de coordinación de ejecutar su código del trabajo que Task.Run transfiere al conjunto de hilos

ACTUALIZAR

  • en respuesta al comentario de @ eran-otzap :
    "para operaciones vinculadas a IO porque el código es más simple y no tiene que protegerse contra las condiciones de carrera" ¿Qué condiciones de carrera pueden ocurrir, podría dar un ejemplo? "

Esta pregunta debería haberse puesto como una publicación separada.

Wikipedia tiene una buena explicación de las condiciones de carrera . La parte necesaria es multiproceso y del mismo artículo de MSDN Programación asincrónica con asíncrono y espera (C # y Visual Basic) :

Los métodos asíncronos están destinados a ser operaciones sin bloqueo. Una expresión de espera en un método asíncrono no bloquea el hilo actual mientras se ejecuta la tarea esperada. En cambio, la expresión registra el resto del método como una continuación y devuelve el control al llamador del método asíncrono.

Las palabras clave asíncronas y en espera no hacen que se creen hilos adicionales. Los métodos asincrónicos no requieren subprocesos múltiples porque un método asincrónico no se ejecuta en su propio hilo. El método se ejecuta en el contexto de sincronización actual y usa el tiempo en el hilo solo cuando el método está activo. Puede usar Task.Run para mover el trabajo vinculado a la CPU a un subproceso en segundo plano, pero un subproceso en segundo plano no ayuda con un proceso que solo está esperando que los resultados estén disponibles.

El enfoque asíncrono de la programación asincrónica es preferible a los enfoques existentes en casi todos los casos. En particular, este enfoque es mejor que BackgroundWorker para operaciones vinculadas a IO porque el código es más simple y no tiene que protegerse contra las condiciones de carrera. En combinación con Task.Run, la programación asincrónica es mejor que BackgroundWorker para operaciones vinculadas a la CPU porque la programación asincrónica separa los detalles de coordinación de ejecutar su código del trabajo que Task.Run transfiere al conjunto de hilos

Es decir, "Las palabras clave asíncronas y en espera no hacen que se creen hilos adicionales".

Hasta donde puedo recordar mis propios intentos cuando estaba estudiando este artículo hace un año, si ha ejecutado y jugado con código de muestra del mismo artículo, podría encontrar situaciones que no sean asincrónicas (podría intentar convertir para ti) ¡bloquea indefinidamente!

Además, para ejemplos concretos, puede buscar en este sitio. Aquí hay algunos ejemplos:

Gennady Vanin Геннадий Ванин
fuente
30
BackgrondWorker no está explícitamente etiquetado como obsoleto en .NET 4.5. El artículo de MSDN simplemente dice que las operaciones vinculadas a IO son mejores con los métodos asincrónicos: el uso de BackgroundWorker no significa que no pueda usar métodos asincrónicos.
Peter Ritchie
@ PeterRitchie, corregí mi respuesta. Para mí, "los enfoques existentes son obsoletos" es sinónimo de "El enfoque asincrónico de la programación asincrónica es preferible a los enfoques existentes en casi todos los casos"
Gennady Vanin Геннадий Ванин
77
Tengo problemas con esa página de MSDN. Por un lado, no hace más "coordinación" con BGW que con Task. Y sí, BGW nunca tuvo la intención de realizar directamente operaciones de E / S: siempre había habido una mejor manera de hacer E / S que en BGW. La otra respuesta muestra que BGW no es más complejo de usar que Task. Y si usa BGW correctamente, no hay condiciones de carrera.
Peter Ritchie
"para operaciones vinculadas a IO porque el código es más simple y no tiene que protegerse contra las condiciones de carrera" ¿Qué condiciones de carrera pueden ocurrir, podría dar un ejemplo?
eran otzap
11
Esta respuesta es incorrecta. La programación asincrónica puede desencadenar demasiado fácilmente puntos muertos en programas no triviales. En comparación, BackgroundWorker es simple y sólido como una roca.
ZunTzu