Diferencia entre await y ContinueWith


Puede alguien explicar si await y ContinueWith son sinónimos o no en el siguiente ejemplo. Estoy tratando de usar TPL por primera vez y he estado leyendo toda la documentación, pero no entiendo la diferencia.

Await:

String webText = await getWebPage(uri);
await parseData(webText);

Continúa con :

Task<String> webText = new Task<String>(() => getWebPage(uri));
Task continue = webText.ContinueWith((task) =>  parseData(task.Result));
webText.Start();
continue.Wait();

¿Se prefiere uno sobre el otro en situaciones particulares?

Author: Spongebob Comrade, 2013-09-23

2 answers

En el segundo código, estás sincrónicamente esperando que la continuación se complete. En la primera versión, el método volverá a la persona que llama tan pronto como llegue a la primera expresión await que no esté ya completada.

Son muy similares en que ambos programan una continuación, pero tan pronto como el flujo de control se vuelve incluso ligeramente complejo, await conduce a mucho código más simple. Además, como señaló Servy en los comentarios, la espera de una tarea " desenvolverá" excepciones agregadas que generalmente conducen a un manejo de errores más simple. También usar await programará implícitamente la continuación en el contexto de llamada (a menos que use ConfigureAwait). No es nada que no se pueda hacer "manualmente", pero es mucho más fácil hacerlo con await.

Sugiero que intente implementar una secuencia de operaciones ligeramente mayor con await y Task.ContinueWith - puede ser una verdadera revelación.

 73
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-23 17:42:05

Aquí está la secuencia de fragmentos de código que utilicé recientemente para ilustrar la diferencia y varios problemas que se solucionan usando soluciones asincrónicas.

Supongamos que tiene algún controlador de eventos en su aplicación basada en GUI que toma mucho tiempo, y por lo que le gustaría hacerlo asincrónico. Aquí está la lógica síncrona con la que comienzas:

while (true) {
    string result = LoadNextItem().Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
        break;
    }
}

LoadNextItem devuelve una Tarea, que eventualmente producirá algún resultado que le gustaría inspeccionar. Si el resultado actual es el que estás buscando, actualizas el valor de algún contador en la interfaz de usuario, y volver desde el método. De lo contrario, continuará procesando más elementos de LoadNextItem.

Primera idea para la versión asíncrona: ¡solo usa continuaciones! Y vamos a ignorar la parte de bucle por el momento. Quiero decir, ¿qué podría salir mal?

return LoadNextItem().ContinueWith(t => {
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
});

Genial, ahora tenemos un método que no bloquea! Se bloquea en su lugar. Cualquier actualización de los controles de la interfaz de usuario debe ocurrir en el subproceso de la interfaz de usuario, por lo que deberá tener en cuenta eso. Afortunadamente, hay una opción para especificar cómo deben programarse las continuaciones, y hay una opción predeterminada para esto:

return LoadNextItem().ContinueWith(t => {
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
},
TaskScheduler.FromCurrentSynchronizationContext());

Genial, ahora tenemos un método que no se bloquea! En su lugar, falla silenciosamente. Las continuaciones son tareas separadas en sí mismas, con su estatus no ligado al de la tarea antecedente. Por lo tanto, incluso si LoadNextItem falla, la persona que llama solo verá una tarea que se haya completado con éxito. Bien, entonces solo pasa la excepción, si hay una:

return LoadNextItem().ContinueWith(t => {
    if (t.Exception != null) {
        throw t.Exception.InnerException;
    }
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
},
TaskScheduler.FromCurrentSynchronizationContext());

Genial, ahora esto en realidad funciona. Para un solo artículo. Ahora, qué tal ese bucle. Resulta que una solución equivalente a la lógica de la versión síncrona original se verá algo como esto:

Task AsyncLoop() {
    return AsyncLoopTask().ContinueWith(t =>
        Counter.Value = t.Result,
        TaskScheduler.FromCurrentSynchronizationContext());
}
Task<int> AsyncLoopTask() {
    var tcs = new TaskCompletionSource<int>();
    DoIteration(tcs);
    return tcs.Task;
}
void DoIteration(TaskCompletionSource<int> tcs) {
    LoadNextItem().ContinueWith(t => {
        if (t.Exception != null) {
            tcs.TrySetException(t.Exception.InnerException);
        } else if (t.Result.Contains("target")) {
            tcs.TrySetResult(t.Result.Length);
        } else {
            DoIteration(tcs);
        }});
}

O, en lugar de todo lo anterior, puedes usar async para hacer lo mismo:

async Task AsyncLoop() {
    while (true) {
        string result = await LoadNextItem();
        if (result.Contains("target")) {
            Counter.Value = result.Length;
            break;
        }
    }
}

Eso es mucho mejor ahora, ¿no?

 71
Author: pkt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-24 13:10:16