2011-04-19 7 views
5

A petición GET corro (algo así):cliente Web de devolución de llamada asincrónica no se llama en ASP.NET MVC

public ActionResult Index(void) { 
    webClient.DownloadStringComplete += onComplete; 
    webClient.DownloadStringAsync(...); 
    return null; 
} 

veo que onComplete no es GET invocado hasta después Index() ha finalizado la ejecución. Veo que onComplete se invoca en un subproceso diferente desde el que se ejecutó Index.

Pregunta: ¿Por qué sucede esto? ¿Por qué el subproceso asincrónico de webClient está aparentemente bloqueado hasta que finaliza el proceso de solicitud de subprocesos?

¿Hay una manera de solucionar este problema sin necesidad de iniciar nuevo hilo de ThreadPool (yo probamos este, y el uso de grupo de subprocesos funciona como se espera. Además de devolución de llamada de webclient ocurre como se espera si DownloadStringAsync se llama a partir de hilos de un ThreadPool).

ASP.NET MVC 3.0, .NET 4.0, MS Cassini servidor web dev (VS 2010)

EDIT: Aquí es un código completo:

public class HomeController : Controller { 
    private static ManualResetEvent done; 

    public ActionResult Index() { 
     return Content(DownloadString() ? "success" : "failure"); 
    } 

    private static bool DownloadString() { 
     try { 
      done = new ManualResetEvent(false); 
      var wc = new WebClient(); 
      wc.DownloadStringCompleted += (sender, args) => { 
       // this breakpoint is not hit until after Index() returns. 
       // It is weird though, because response isn't returned to the client (browser) until this callback finishes. 
       // Note: This thread is different from one Index() was running on. 
       done.Set(); 
      }; 

      var uri = new Uri(@"http://us.battle.net/wow/en/character/blackrock/hunt/simple"); 

      wc.DownloadStringAsync(uri); 

      var timedout = !done.WaitOne(3000); 
      if (timedout) { 
       wc.CancelAsync(); 
       // if this would be .WaitOne() instead then deadlock occurs. 
       var timedout2 = !done.WaitOne(3000); 
       Console.WriteLine(timedout2); 
       return !timedout2; 
      } 
      return true; 
     } 
     catch (Exception ex) { 
      Console.WriteLine(ex.Message); 
     } 
     return false; 
    } 
} 

Respuesta

5

Tenía curiosidad acerca de esto, así que le preguntó sobre la discusión interna ASP.NET alias de Microsoft, y tiene esta respuesta de Levi Broderick:

ASP.NET utiliza internamente el SynchronizationContext para sincronización, y sólo un hilo a la vez puede tener el control de ese bloqueo. En su ejemplo particular , el subproceso que ejecuta HomeController :: DownloadString contiene el bloqueo, pero está esperando a que se active el ManualResetEvent. El ManualResetEvent no se disparará hasta el método DownloadStringCompleted carreras, pero ese método se ejecuta en un hilo diferente que no puede nunca tener el bloqueo de sincronización debido a que el primer hilo conserva dentro y fuera. Estás ahora estancado.

Me sorprende que esto haya funcionado alguna vez en MVC 2, pero si lo hizo fue solo por feliz accidente. Esto nunca fue compatible con .

1

Este es el punto de utilizar procesamiento asincrónico. Tu hilo principal inicia la llamada, luego continúa para hacer otras cosas útiles. Cuando se completa la llamada, selecciona un hilo del grupo de subprocesos de finalización de IO y llama a su método de devolución de llamada registrado (en este caso su método onComplete). De esta forma, no es necesario tener un hilo costoso esperando que se complete una llamada web de larga duración.

De todos modos, los métodos que está utilizando siguen el Patrón asincrónico basado en eventos. Puede leer más sobre esto aquí: http://msdn.microsoft.com/en-us/library/wewwczdw.aspx

(corregir) Nota: No tenga en cuenta esta respuesta ya que no ayuda a responder la pregunta aclarada. Dejándolo para la discusión que pasó debajo de él.

+0

que lo explica. Entonces, ¿WebClient no usa ThreadPool? ¿Cómo hace cola WebClient una solicitud? Lo vadeé con reflector por un tiempo, pero no pude encontrar dónde sucede. –

+0

No está poniendo en cola una solicitud. En realidad, comienza la solicitud en ese momento. Pero después de iniciar la solicitud, DownloadStringAsync() regresará y le permitirá hacer otras cosas mientras se realiza la descarga. ¡Durante la descarga, en realidad no hay ningún hilo presente! Solo saca un hilo de la lista de hilos para avisarte de que se ha completado. – RandomEngy

+0

Ya veo. Entonces, ¿en qué parte del código se invoca la devolución de llamada? Traté de poner Thread.Sleep (10000) antes de regresar de Index, pero la devolución de llamada no se invoca hasta que Index vuelva. Lo que significa que la llamada de devolución de llamada se pone en cola después de la llamada de índice. Tengo curiosidad de cómo sucede eso. Si se llama a la devolución de llamada en CLR ThreadPool, no se debe bloquear mediante el hilo de procesamiento de solicitud GET, ¿o sí? –

Cuestiones relacionadas