example - metodos async c# mvc
Controlador ASP.NET: un módulo o controlador asincrónico completado mientras una operación asincrónica aún estaba pendiente (7)
Tengo un controlador ASP.NET MVC 4 muy simple:
public class HomeController : Controller
{
private const string MY_URL = "http://smthing";
private readonly Task<string> task;
public HomeController() { task = DownloadAsync(); }
public ActionResult Index() { return View(); }
private async Task<string> DownloadAsync()
{
using (WebClient myWebClient = new WebClient())
return await myWebClient.DownloadStringTaskAsync(MY_URL)
.ConfigureAwait(false);
}
}
Cuando comienzo el proyecto, veo mi vista y se ve bien, pero cuando actualizo la página aparece el siguiente error:
[InvalidOperationException: un módulo o controlador asincrónico completado mientras una operación asincrónica aún estaba pendiente.]
¿Por que sucede? Hice un par de pruebas:
-
Si eliminamos
task = DownloadAsync();
desde el constructor y ponerlo en el métodoIndex
funcionará bien sin los errores. -
Si utilizamos otro
DownloadAsync()
elreturn await Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; });
cuerporeturn await Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; });
Funcionará correctamente.
¿Por qué es imposible usar el método
WebClient.DownloadStringTaskAsync
dentro de un constructor del controlador?
ASP.NET considera ilegal iniciar una "operación asincrónica" vinculada a su
SynchronizationContext
y devolver un
ActionResult
antes de que se
ActionResult
todas las operaciones iniciadas.
Todos los métodos
async
registran como "operaciones asincrónicas", por lo que debe asegurarse de que todas las llamadas que se unen al ASP.NET
SynchronizationContext
completen antes de devolver un
ActionResult
.
En su código, regresa sin asegurarse de que
DownloadAsync()
haya ejecutado hasta su finalización.
Sin embargo, guarda el resultado en el miembro de la
task
, por lo que asegurarse de que esté completo es muy fácil.
Simplemente ponga la
await task
en todos sus métodos de acción (después de sincronizarlos) antes de regresar:
public async Task<ActionResult> IndexAsync()
{
try
{
return View();
}
finally
{
await task;
}
}
EDITAR:
En algunos casos, es posible que deba llamar a un método
async
que
no debe completarse antes de regresar a ASP.NET
.
Por ejemplo, es posible que desee inicializar perezosamente una tarea de servicio en segundo plano que debería continuar ejecutándose después de que se complete la solicitud actual.
Este no es el caso del código del OP porque el OP desea que la tarea se complete antes de regresar.
Sin embargo, si necesita comenzar y no esperar una tarea, hay una manera de hacerlo.
Simplemente debe usar una técnica para "escapar" del actual
SynchronizationContext.Current
.
-
( no recomendado ) Una característica de
Task.Run()
es escapar del contexto de sincronización actual. Sin embargo, la gente recomienda no usar esto en ASP.NET porque el conjunto de hilos de ASP.NET es especial. Además, incluso fuera de ASP.NET, este enfoque da como resultado un cambio de contexto adicional. -
( recomendado ) Una forma segura de escapar del contexto de sincronización actual sin forzar un cambio de contexto adicional o molestar el conjunto de hilos de ASP.NET inmediatamente es establecer
SynchronizationContext.Current
ennull
, llamar a su métodoasync
y luego restaurar el valor original .
El método myWebClient.DownloadStringTaskAsync se ejecuta en un hilo separado y no es bloqueante. Una posible solución es hacer esto con el controlador de eventos DownloadDataCompleted para myWebClient y un campo de clase SemaphoreSlim.
private SemaphoreSlim signalDownloadComplete = new SemaphoreSlim(0, 1);
private bool isDownloading = false;
....
//Add to DownloadAsync() method
myWebClient.DownloadDataCompleted += (s, e) => {
isDownloading = false;
signalDownloadComplete.Release();
}
isDownloading = true;
...
//Add to block main calling method from returning until download is completed
if (isDownloading)
{
await signalDownloadComplete.WaitAsync();
}
El método return
async Task
, y
ConfigureAwait(false)
puede ser una de las soluciones.
Actuará como vacío asíncrono y no continuará con el contexto de sincronización (siempre y cuando realmente no le importe el resultado final del método)
En Async Void, ASP.Net y Count of Outstanding Operations , Stephan Cleary explica la raíz de este error:
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 asincrónicos notifican al SynchronizationContext de su inicio y finalización.
Lo que está sucediendo es que está disparando
DownloadAsync
dentro de su constructor de clase, donde dentro de usted
await
la llamada http asíncrona.
Esto registra la operación asincrónica con ASP.NET
SynchronizationContext
.
Cuando su
HomeController
regresa, ve que tiene una operación asincrónica pendiente que aún no se ha completado, y es por eso que genera una excepción.
Si eliminamos task = DownloadAsync (); desde el constructor y ponerlo en el método Index funcionará bien sin los errores.
Como expliqué anteriormente, eso se debe a que ya no tiene una operación asincrónica pendiente mientras regresa del controlador.
Si utilizamos otro DownloadAsync () el retorno del cuerpo espera
Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; });
Funcionará correctamente.
Eso es porque
Task.Factory.StartNew
hace algo peligroso en ASP.NET.
No registra la ejecución de tareas con ASP.NET.
Esto puede conducir a casos extremos en los que se ejecuta un reciclaje de grupo, ignorando por completo su tarea en segundo plano, causando un aborto anormal.
Es por eso que debe usar un mecanismo que registre la tarea, como
HostingEnvironment.QueueBackgroundWorkItem
.
Es por eso que no es posible hacer lo que estás haciendo, como lo estás haciendo.
Si realmente desea que esto se ejecute en un subproceso en segundo plano, en un estilo de "disparar y olvidar", use
HostingEnvironment
(si está en .NET 4.5.2) o
BackgroundTaskManager
.
Tenga en cuenta que al hacer esto, está utilizando un subproceso de grupo de subprocesos para realizar operaciones de E / S asíncronas, que es redundante y exactamente lo que IO
async-await
intentos de
async-await
para superar.
Me encontré con un problema relacionado. Un cliente está utilizando una interfaz que devuelve Tarea y se implementa con asíncrono.
En Visual Studio 2015, el método del cliente que es asíncrono y que no utiliza la palabra clave de espera cuando se invoca el método no recibe ninguna advertencia o error, el código se compila limpiamente. Una condición de carrera es promovida a producción.
Tuve un problema similar, pero se resolvió pasando un CancellationToken como parámetro al método asíncrono.
Ejemplo de notificación por correo electrónico con archivo adjunto.
public async Task SendNotification(string SendTo,string[] cc,string subject,string body,string path)
{
SmtpClient client = new SmtpClient();
MailMessage message = new MailMessage();
message.To.Add(new MailAddress(SendTo));
foreach (string ccmail in cc)
{
message.CC.Add(new MailAddress(ccmail));
}
message.Subject = subject;
message.Body =body;
message.Attachments.Add(new Attachment(path));
//message.Attachments.Add(a);
try {
message.Priority = MailPriority.High;
message.IsBodyHtml = true;
await Task.Yield();
client.Send(message);
}
catch(Exception ex)
{
ex.ToString();
}
}