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 asyncy utiliza las awaitpalabras 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 awaitsí 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 awaitpalabra 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 = truese ejecutará en el grupo de subprocesos, mientras que en el segundo ejemplo podría ejecutarse en el mismo subproceso que invocóGetSomeObjectByToken(si teníaSynchronizationContextinstalado, por ejemplo, era un subproceso de interfaz de usuario). El comportamiento siGetSomeObjectByTokenAsyncarroja una excepción también será ligeramente diferente. En general,awaitse prefiereContinueWith, ya que casi siempre es más legible.Respuestas:
El mecanismo
async/awaithace que el compilador transforme su código en una máquina de estado. Su código se ejecutará sincrónicamente hasta el primeroawaitque 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
awaitse 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
Tasks si ese es el tipo de objetos a esperar utilizados en lasawaitexpresiones.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/awaityContinueWith:async/awaitprogramaré continuaciones enSynchronizationContext.Currentcaso de existir, de lo contrario enTaskScheduler.Current1ContinueWithprogramará continuaciones en el programador de tareas proporcionado oTaskScheduler.Currenten 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/awaitfunciona 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
TaskoTask<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?.ContinueWithestaba 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 conTasklas propiedades, laAggregateExceptioncomplejidad 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 laGetOrAddllamada 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/awaitcon C # 5 en 2012. Como herramienta es detallada, no es fácilmente composable, y requiere un trabajo adicional para desenvolverAggregateExceptionsyTask<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/awaitpor alguna razón (como cuando está en un método conoutparámetros ).Actualización: eliminé los consejos engañosos que
ContinueWithdeberían usarTaskScheduler.Defaultpara imitar el comportamiento predeterminado deawait. En realidad laawaitde las programaciones predeterminadas usando su continuaciónTaskScheduler.Current.fuente