Esto es lo que quiero decir:
public Task<SomeObject> GetSomeObjectByTokenAsync(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return Task.FromResult(new SomeObject()
{
IsAuthorized = false
});
}
else
{
return repository.GetSomeObjectByTokenAsync(token).ContinueWith(t =>
{
t.Result.IsAuthorized = true;
return t.Result;
});
}
}
Por encima de método puede ser esperado y creo que se asemeja mucho a lo que el T pide a base de una sincrónica P attern sugiere hacer? (Los otros patrones que conozco son los patrones APM y EAP ).
Ahora, ¿qué pasa con el siguiente código:
public async Task<SomeObject> GetSomeObjectByToken(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return new SomeObject()
{
IsAuthorized = false
};
}
else
{
SomeObject result = await repository.GetSomeObjectByTokenAsync(token);
result.IsAuthorized = true;
return result;
}
}
Las diferencias clave aquí son que el método es async
y utiliza las await
palabras clave, entonces, ¿qué cambia esto en contraste con el método escrito anteriormente? Sé que también puede ser esperado. Cualquier método que devuelva Tarea puede, en realidad, a menos que me equivoque.
Soy consciente de la máquina de estado creada con esas instrucciones de cambio cada vez que un método se etiqueta como async
, y sé que en await
sí mismo no usa ningún hilo: no se bloquea en absoluto, el hilo simplemente va a hacer otras cosas, hasta que sea volvió a llamar para continuar la ejecución del código anterior.
Pero, ¿cuál es la diferencia subyacente entre los dos métodos, cuando los invocamos usando la await
palabra clave? ¿Hay alguna diferencia, y si la hay, cuál es la preferida?
EDIT: Me siento como el primer fragmento de código se prefiere, ya que efectivamente eludir la asíncrono / Await palabras clave, sin ninguna repercusión - volvemos una tarea que continuará su ejecución de forma sincrónica, o una tarea ya realizada en el camino caliente (que puede ser en caché).
result.IsAuthorized = true
se ejecutará en el grupo de subprocesos, mientras que en el segundo ejemplo podría ejecutarse en el mismo subproceso que invocóGetSomeObjectByToken
(si teníaSynchronizationContext
instalado, por ejemplo, era un subproceso de interfaz de usuario). El comportamiento siGetSomeObjectByTokenAsync
arroja una excepción también será ligeramente diferente. En general,await
se prefiereContinueWith
, ya que casi siempre es más legible.Respuestas:
El mecanismo
async
/await
hace que el compilador transforme su código en una máquina de estado. Su código se ejecutará sincrónicamente hasta el primeroawait
que llegue a un elemento que no se haya completado, si lo hubiera.En el compilador de Microsoft C #, esta máquina de estado es un tipo de valor, lo que significa que tendrá un costo muy pequeño cuando todos
await
se completen, ya que no asignará un objeto y, por lo tanto, no generará basura. Cuando no se completa ninguno de estos, este tipo de valor inevitablemente se encuadra.Tenga en cuenta que esto no evita la asignación de
Task
s si ese es el tipo de objetos a esperar utilizados en lasawait
expresiones.Con
ContinueWith
, solo evita las asignaciones (que no seanTask
) si su continuación no tiene un cierre y si no utiliza un objeto de estado o reutiliza un objeto de estado tanto como sea posible (por ejemplo, de un grupo).Además, se llama a la continuación cuando se completa la tarea, creando un marco de pila, no se alinea. El marco intenta evitar desbordamientos de pila, pero puede haber un caso en el que no evitará uno, como cuando se asignan grandes matrices.
La forma en que trata de evitar esto es verificando cuánta pila queda y, si por alguna medida interna la pila se considera llena, programa la continuación para ejecutarse en el programador de tareas. Intenta evitar excepciones fatales de desbordamiento de pila a costa del rendimiento.
Aquí hay una sutil diferencia entre
async
/await
yContinueWith
:async
/await
programaré continuaciones enSynchronizationContext.Current
caso de existir, de lo contrario enTaskScheduler.Current
1ContinueWith
programará continuaciones en el programador de tareas proporcionado oTaskScheduler.Current
en las sobrecargas sin el parámetro del programador de tareasPara simular
async
/await
's comportamiento por defecto:Para simular
async
/await
'comportamiento s conTask
' s.ConfigureAwait(false)
:Las cosas comienzan a complicarse con los bucles y el manejo de excepciones. Además de mantener su código legible,
async
/await
funciona con cualquier espera .Su caso se maneja mejor con un enfoque mixto: un método sincrónico que llama a un método asincrónico cuando es necesario. Un ejemplo de su código con este enfoque:
En mi experiencia, he encontrado muy pocos lugares en el código de la aplicación donde agregar tal complejidad realmente paga el tiempo para desarrollar, revisar y probar dichos enfoques, mientras que en el código de la biblioteca cualquier método puede ser un cuello de botella.
El único caso en el que tiendo tareas Elide es cuando una
Task
oTask<T>
regresar método simplemente devuelve el resultado de otro método asíncrono, sin que ella misma de haber realizado cualquier I / O o cualquier post-procesamiento.YMMV.
ConfigureAwait(false)
o aguarde a alguien que use una programación personalizadafuente
private sealed class <Main>d__0 : IAsyncStateMachine
. Sin embargo, las instancias de esta clase pueden ser reutilizables.ContinueWith
?.ContinueWith
estaba destinado a usarse mediante programación. Especialmente si maneja tareas intensivas de CPU en lugar de tareas de E / S. Pero cuando llegas al punto de tener que lidiar conTask
las propiedades, laAggregateException
complejidad de las condiciones de manejo, los ciclos y el manejo de excepciones cuando se invocan métodos asincrónicos adicionales, casi no hay razón para seguir con eso.Task.FromResult("...")
. En el código asíncrono, si almacena en caché los valores por clave que requieren E / S para obtenerlos, puede usar un diccionario donde los valores son tareas, por ejemplo, enConcurrentDictionary<string, Task<T>>
lugar deConcurrentDictionary<string, T>
usar laGetOrAdd
llamada con una función de fábrica y esperar su resultado. Esto garantiza que solo se realiza una solicitud de E / S para llenar la clave de la memoria caché, despierta a los camareros tan pronto como se completa la tarea y sirve como una tarea completada después.Al usar
ContinueWith
, está usando las herramientas que estaban disponibles antes de la introducción de la funcionalidadasync
/await
con C # 5 en 2012. Como herramienta es detallada, no es fácilmente composable, y requiere un trabajo adicional para desenvolverAggregateException
syTask<Task<TResult>>
devolver valores (los obtiene cuando pasa delegados asincrónicos como argumentos). Ofrece pocas ventajas a cambio. Puede considerar usarlo cuando quiera adjuntar múltiples continuaciones a la mismaTask
, o en algunos casos raros donde no puede usarasync
/await
por alguna razón (como cuando está en un método conout
parámetros ).Actualización: eliminé los consejos engañosos que
ContinueWith
deberían usarTaskScheduler.Default
para imitar el comportamiento predeterminado deawait
. En realidad laawait
de las programaciones predeterminadas usando su continuaciónTaskScheduler.Current
.fuente