2009-08-08 17 views
8

Ho un'applicazione ASP.NET MVC che attualmente utilizza la classe WebClient per effettuare una semplice chiamata a un servizio Web esterno da un'azione del controller.Utilizzo di WebClient all'interno di ASP.NET MVC in modo asincrono?

Attualmente sto utilizzando il metodo DownloadString, che viene eseguito in modo sincrono. Ho riscontrato problemi in cui il servizio Web esterno non risponde, il che ha come conseguenza la mia intera applicazione ASP.NET essere affamata di thread e non risponde.

Qual è il modo migliore per risolvere questo problema? Esiste un metodo DownloadStringAsync, ma non sono sicuro di come chiamarlo dal controller. Devo usare la classe AsyncController? In tal caso, come interagiscono il metodo AsyncController e DownloadStringAsync?

Grazie per l'aiuto.

risposta

7

Penso che l'utilizzo di AsyncControllers vi aiuterà in questo modo mentre scaricano l'elaborazione dal thread di richiesta.

userei qualcosa di simile (utilizzando il modello evento come descritto nella this article):

public class MyAsyncController : AsyncController 
{ 
    // The async framework will call this first when it matches the route 
    public void MyAction() 
    { 
     // Set a default value for our result param 
     // (will be passed to the MyActionCompleted method below) 
     AsyncManager.Parameters["webClientResult"] = "error"; 
     // Indicate that we're performing an operation we want to offload 
     AsyncManager.OutstandingOperations.Increment(); 

     var client = new WebClient(); 
     client.DownloadStringCompleted += (s, e) => 
     { 
      if (!e.Cancelled && e.Error == null) 
      { 
       // We were successful, set the result 
       AsyncManager.Parameters["webClientResult"] = e.Result; 
      } 
      // Indicate that we've completed the offloaded operation 
      AsyncManager.OutstandingOperations.Decrement(); 
     }; 
     // Actually start the download 
     client.DownloadStringAsync(new Uri("http://www.apple.com")); 
    } 

    // This will be called when the outstanding operation(s) have completed 
    public ActionResult MyActionCompleted(string webClientResult) 
    { 
     ViewData["result"] = webClientResult; 
     return View(); 
    } 
} 

e assicurarsi di configurazione qualunque percorsi è necessario, ad esempio (in Global.asax.cs):

public class MvcApplication : System.Web.HttpApplication 
{ 
    public static void RegisterRoutes(RouteCollection routes) 
    { 
     routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 

     routes.MapAsyncRoute(
      "Default", 
      "{controller}/{action}/{id}", 
      new { controller = "Home", action = "Index", id = "" } 
     ); 
    } 
} 
+0

c'è un modo per aggiungere un timeout alla chiamata asincrona del client web? –

+1

Solo una nota, sembra che questa risposta sia stata postata nel 09 in MVC 1.0 giorni. Ora con MVC 2/3 la risposta è LEGGERMENTE diversa. Il metodo MapAsyncRoute è sparito e non è più necessario. Inoltre, il metodo MyAction deve ora essere rinominato MyActionAsync.Altrimenti, tutto funziona allo stesso modo. – BFree

3

Il metodo DownloadStringAsync utilizza un modello evento, sollevando lo DownloadStringCompleted al termine. Puoi anche interrompere la richiesta se impiega troppo tempo chiamando lo WebClient.CancelAsync(). Ciò consentirà al thread di richiesta principale e al thread WebClient di essere eseguiti in parallelo e consentirà di decidere esattamente per quanto tempo il thread principale dovrà attendere prima di tornare.

Nell'esempio seguente, iniziamo il download e impostiamo il gestore di eventi che vogliamo richiamato quando è finito. DownloadStringAsync ritorna immediatamente, quindi possiamo continuare a elaborare il resto della nostra richiesta.

Per dimostrare un controllo più granulare su questa operazione, quando raggiungiamo la fine dell'azione del controller, possiamo controllare se il download è ancora completo; in caso contrario, dargli altri 3 secondi e poi interrompere.

string downloadString = null; 

ActionResult MyAction() 
{ 
    //get the download location 
    WebClient client = StartDownload(uri); 
    //do other stuff 
    CheckAndFinalizeDownload(client); 
    client.Dispose(); 
} 

WebClient StartDownload(Uri uri) 
{ 
    WebClient client = new WebClient(); 
    client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(Download_Completed); 
    client.DownloadStringAsync(uri); 
    return client; 
} 

void CheckAndFinalizeDownload(WebClient client) 
{ 
    if(this.downloadString == null) 
    { 
     Thread.Sleep(3000); 
    } 
    if(this.downloadString == null) 
    { 
     client.CancelAsync(); 
     this.downloadString = string.Empty; 
    } 
} 

void Download_Completed(object sender, DownloadStringCompletedEventArgs e) 
{ 
    if(!e.Cancelled && e.Error == null) 
    { 
     this.downloadString = (string)e.Result; 
    } 
} 
+0

Credo che questo approccio bloccherà comunque il thread di richiesta: si è ancora sul thread di richiesta durante la chiamata Sleep. I controller asincroni aiuteranno qui perché liberano il thread di richiesta mentre aspetti la risorsa esterna. –

+0

@ Michael davvero. Questo approccio verrà eseguito in parallelo con il thread di richiesta a meno che la richiesta di WebClient non sia terminata al momento in cui il thread del controller ha fatto tutto il resto che deve fare - a quel punto il controllore sceglie se bloccare/dormire o continuare. Non ho avuto l'impressione che la domanda riguardi specificamente come utilizzare AsyncController, ma piuttosto come rendere il WebClient funzionante in modo asincrono. Potrebbe essere una lettura errata della domanda. –

+0

@Rex Sì, penso che dalla descrizione dell'app Web sia "thread-starved and nonresponsive" che è probabile che l'OP stia esaurendo i thread di richiesta. Limitare la risposta esterna a 3 secondi aiuterà un po '(se le vostre richieste esterne richiedevano in precedenza più tempo di quello), ma è ancora molto tempo per mantenere il thread e quindi non si ridimensionerà. –

Problemi correlati