c# - method - Async Void, ASP.Net y recuento de operaciones pendientes
task c# (1)
Microsoft tomó la decisión de evitar la mayor cantidad de problemas de compatibilidad con versiones anteriores cuando incorporase la función async
en ASP.NET. Y querían llevarlo a todos sus "un ASP.NET", de modo que async
soporte para WinForms, MVC, WebAPI, SignalR, etc.
Históricamente, ASP.NET ha admitido operaciones asincrónicas limpias desde .NET 2.0 a través del Patrón asincrónico basado en eventos (EAP), en el que los componentes asíncronos notifican al SynchronizationContext
su inicio y finalización. .NET 4.5 trae los primeros cambios bastante importantes a este soporte, actualizando los tipos asincrónicos principales de ASP.NET para habilitar mejor el Patrón asincrónico basado en tareas (TAP, es decir, async
).
Mientras tanto, cada marco diferente (WebForms, MVC, etc.) desarrolló su propia forma de interactuar con ese núcleo, manteniendo la compatibilidad con versiones anteriores como una prioridad. En un intento de ayudar a los desarrolladores, el núcleo de ASP.NET SynchronizationContext
fue mejorado con la excepción que está viendo; atrapará muchos errores de uso.
En el mundo de WebForms, tienen RegisterAsyncTask
pero mucha gente simplemente usa manejadores de eventos async void
. Por lo tanto, ASP.NET SynchronizationContext
permitirá la async void
SynchronizationContext
en los momentos apropiados durante el ciclo de vida de la página, y si la usa en un momento inadecuado, generará esa excepción.
En el mundo MVC / WebAPI / SignalR, los marcos están más estructurados como servicios. Así que pudieron adoptar la async Task
de una manera muy natural, y el marco solo tiene que lidiar con la Task
devuelta, una abstracción muy limpia. Como nota al margen, ya no necesitas AsyncController
; MVC sabe que es asincrónico solo porque devuelve una Task
.
Sin embargo, si intentas devolver una Task
y usar un async void
, eso no es compatible. Y hay pocas razones para apoyarlo; Sería bastante complejo solo apoyar a los usuarios que se supone que no deben hacer eso de todos modos. Recuerde que async void
notifica al núcleo ASP.NET SynchronizationContext
directamente, omitiendo completamente el framework MVC. El marco de MVC entiende cómo esperar su Task
pero ni siquiera conoce el async void
, por lo que devuelve la finalización al núcleo de ASP.NET, que ve que en realidad no está completo.
Esto puede causar problemas en dos escenarios:
- Estás tratando de usar alguna biblioteca o lo que sea que use el
async void
. Lo siento, pero el hecho es que la biblioteca está rota y tendrá que ser reparada. - Está envolviendo un componente EAP en una
Task
y utilizando adecuadamenteawait
. Esto puede causar problemas porque el componente EAP interactúa directamente conSynchronizationContext
. En este caso, la mejor solución es modificar el tipo para que sea compatible con TAP de forma natural o sustituirlo por un tipo de TAP (por ejemplo,HttpClient
lugar deWebClient
). De lo contrario, puede usar TAP-over-APM en lugar de TAP-over-EAP. Si ninguno de estos es factible, puede usarTask.Run
alrededor de suTask.Run
TAP-over-EAP.
En cuanto a "disparar y olvidar":
Personalmente, nunca uso esta frase para los métodos de async void
. Por un lado, la semántica de manejo de errores ciertamente no encaja con la frase "disparar y olvidar"; Medio en broma me refiero a los métodos de async void
como "fuego y bloqueo". Un verdadero método async
"disparar y olvidar" sería un método de async Task
en el que ignoraría la Task
devuelta en lugar de esperarla.
Dicho esto, en ASP.NET casi nunca desea regresar temprano de las solicitudes (que es lo que implica "disparar y olvidar"). Esta respuesta ya es demasiado larga, pero tengo una descripción de los problemas en mi blog, junto con algún código para apoyar ASP.NET "disparar y olvidar" si es realmente necesario.
Estoy tratando de entender por qué un método de vacío asíncrono en una aplicación ASP.Net puede dar como resultado la siguiente excepción, mientras que parece que la tarea asíncrona no:
System.InvalidOperationException: An asynchronous module or handler
completed while an asynchronous operation was still pending
Soy relativamente nuevo en el mundo de la asincronización en .NET, pero siento que he tratado de ejecutar esto a través de una serie de recursos existentes, que incluyen todo lo siguiente:
- ¿Cuál es la diferencia entre devolver el vacío y devolver una Tarea?
- Se trata del SynchronizationContext
- Sugerencias asincrónicas de azúcar sintáctica
- Async en ASP.NET
A partir de estos recursos, entiendo que la mejor práctica es típicamente devolver Tarea y evitar el vacío asíncrono. También entiendo que async void incrementa el recuento de operaciones pendientes cuando se llama al método y lo disminuye cuando se completa. Esto parece al menos parte de la respuesta a mi pregunta. Sin embargo, lo que me falta es lo que sucede cuando devuelvo Tarea y por qué hacerlo "funciona".
Aquí hay un ejemplo artificial para ilustrar más mi pregunta:
public class HomeController : AsyncController
{
// This method will work fine
public async Task<ActionResult> ThisPageWillLoad()
{
// Do not await the task since it is meant to be fire and forget
var task = this.FireAndForgetTask();
return await Task.FromResult(this.View("Index"));
}
private async Task FireAndForgetTask()
{
var task = Task.Delay(TimeSpan.FromSeconds(3));
await task;
}
// This method will throw the following exception:
// System.InvalidOperationException: An asynchronous module or
// handler completed while an asynchronous operation was still pending
public async Task<ActionResult> ThisPageWillNotLoad()
{
// Obviously can''t await a void method
this.FireAndForgetVoid();
return await Task.FromResult(this.View("Index"));
}
private async void FireAndForgetVoid()
{
var task = Task.Delay(TimeSpan.FromSeconds(3));
await task;
}
}
En una nota relacionada, si mi comprensión del vacío asíncrono es correcta, entonces ¿no está mal pensar en un vacío asincrónico como "disparar y olvidar" en este escenario ya que ASP.Net en realidad no se está olvidando de eso?