operadores mvc metodos metodo example ejemplo controlador carece await async asincrono c# .net asp.net-mvc-4 async-await webclient

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:

  1. Si eliminamos task = DownloadAsync(); desde el constructor y ponerlo en el método Index funcionará bien sin los errores.
  2. Si utilizamos otro DownloadAsync() el return await Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; }); cuerpo return 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 en null , llamar a su método async 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(); } }