¿Mezcla eficiente de métodos de sincronización y asíncrona dentro de un solo método?

11

De acuerdo, suena extraño, pero el código es muy simple y explica bien la situación.

public virtual async Task RemoveFromRoleAsync(AzureTableUser user, string role)
{
    AssertNotDisposed();
    var roles = await GetRolesForUser(user);
    roles.Roles = RemoveRoles(roles.Roles, role);
    await Run(TableOperation.Replace(roles));
}

(Sé que estoy hablando más o menos en el resumen a continuación, pero lo anterior es un método real de lo que será el código de producción real que realmente está haciendo lo que estoy preguntando aquí, y estoy realmente interesado en que realmente revise para la corrección con respecto al patrón asíncrono / espera).

Me encuentro con este patrón cada vez más a menudo ahora que estoy usando async/ awaitmore. El patrón consta de la siguiente cadena de eventos:

  1. Espero una llamada inicial que me proporciona información sobre la que necesito trabajar
  2. Trabaja en esa información sincrónicamente
  3. Espere una llamada final que guarde el trabajo actualizado

El bloque de código anterior es típicamente cómo hago para manejar estos métodos. Yo awaitla primera llamada, que tengo que hacerlo porque es asíncrona. A continuación, hago el trabajo que necesito hacer, que no está vinculado a recursos ni a IO, por lo que no es asíncrono. Finalmente, guardo mi trabajo, que también es una asyncllamada, y fuera del culto a la carga await.

¿Pero es esta la forma más eficiente / correcta de manejar este patrón? Me parece que podría saltarme awaitla última llamada, pero ¿y si falla? ¿Y debo usar un Taskmétodo como ContinueWithencadenar mi trabajo sincrónico con la llamada original? Ahora mismo estoy en un punto en el que no estoy seguro de si estoy manejando esto correctamente.

Dado el código en el ejemplo , ¿hay una mejor manera de manejar esta cadena de llamadas del método async / sync / async?

Estafado
fuente
El código me parece lo más breve y comprensible posible. Cualquier cosa que se me ocurra introduce una complejidad innecesaria.
Eufórico
@Eufórico: ¿Debería molestarme en esperar mi última llamada? ¿Qué pasaría si no lo hiciera y arrojara? ¿Sería diferente de lo que es actualmente?
Estafado el

Respuestas:

3

Sí, creo que esta es la forma correcta de hacerlo.

No puedes saltarte el segundo await. Si lo hiciera, el método parecería completarse demasiado pronto (antes de que se realizara realmente la eliminación), y nunca descubriría si la eliminación falló.

No veo cómo ayudaría ContinueWith()nada de eso aquí. Puede usarlo para evitar el uso await, pero haría que su código sea más complicado y menos legible. Y ese es el objetivo de await: simplificar la escritura de código asincrónico, en comparación con el uso de continuaciones.

svick
fuente
0

La forma de manejar este patrón es garantizar que todas las E / S sean asíncronas. Los métodos de E / S síncronas hacen que el subproceso actual se bloquee mientras espera una respuesta del destino de E / S (red, sistema de archivos, etc.).

Otra cosa a tener en cuenta es que awaitdebe usarse cuando necesite un valor de retorno o cuando necesite que el código esperado termine antes de hacer otra cosa. Si no necesita ninguna de esas cosas, puede "disparar y olvidar" su método asincrónico Task.Run.

Por lo tanto, para el uso más eficiente de los recursos informáticos, si hay RemoveRolesalguna E / S, debería convertirse await RemoveRolesAsyncy los métodos de E / S invocados RemoveRolesAsynctambién deberían ser asíncronos (y posiblemente esperados).

Si el rendimiento no es su mayor preocupación, entonces está bien hacer algunas E / S sincrónicas en un hilo asíncrono. Sin embargo, es una deuda técnica. (En este caso, es posible que desee llamar al primer método asíncrono ConfigureAwait, según dónde se esté ejecutando el código).

Aquí hay una mirada más profunda a las mejores prácticas: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Aquí hay algunas notas sobre el comportamiento de ConfigureAwait en diferentes entornos como ASP.NET, WebAPI, etc. - /programming/13489065/best-practice-to-call-configureawait-for-all-server-side -código

Wayne Bloss
fuente
2
Nunca debe disparar y olvidar el código con Task.Run. Todas las tareas deben ser esperadas. Si no espera la Tarea, y la Tarea se recolecta basura en un estado en el que la excepción es "no observada", provocará una excepción UnobservedTaskException en el tiempo de ejecución y podría bloquear su aplicación dependiendo de la versión del marco.
Triynko