2015-07-13 7 views
11

All'interno di un progetto C# Sto facendo alcune chiamate a una web api, il fatto è che le sto facendo all'interno di un ciclo in un metodo. Di solito non ce ne sono così tanti, anche se pensavo di sfruttare il parallelismo.Dove usare la concorrenza quando si chiama un'API

Quello che sto cercando finora è

public void DeployView(int itemId, string itemCode, int environmentTypeId) 
{ 
    using (var client = new HttpClient()) 
    { 
     client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ApiUrl"]); 
     client.DefaultRequestHeaders.Accept.Clear(); 
     client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 

     var agents = _agentRepository.GetAgentsByitemId(itemId); 

     var tasks = agents.Select(async a => 
      { 
       var viewPostRequest = new 
        { 
         AgentId = a.AgentId, 
         itemCode = itemCode, 
         EnvironmentId = environmentTypeId 
        }; 

       var response = await client.PostAsJsonAsync("api/postView", viewPostRequest); 
      }); 

     Task.WhenAll(tasks); 
    } 
} 

a meno di chiedersi se questo è il percorso corretto, o dovrei cercare di parallelo l'intero DeployView (cioè anche prima di utilizzare il HttpClient)

Ora che ho vederlo pubblicato, mi sa che non posso semplicemente rimuovere la risposta variabile così, basta fare l'attendono, senza impostarla a qualsiasi variabile

Grazie

+2

Beh, in realtà questa è una buona direzione. Ma hai dimenticato la parte più importante. Devi * attendere * i risultati, ad es. * Attendi Task.WhenAll * ma poi devi aggiungere la parola chiave 'async' alla tua funzione DeployView. È meglio fare un tour profondo del paradigma [async/await] (https://msdn.microsoft.com/en-us/library/hh191443.aspx). – ckruczek

+1

qual è il problema/eccezione che stai affrontando? Sono d'accordo anche con ckruczek, non c'è niente di sbagliato nella direzione che stai prendendo..also, vuoi ottenere le risposte? – ojf

+0

Vorrei ricevere le risposte, sì. Ma non sai come usarli se sono tutti Ok – mitomed

risposta

5

Quello che stai introducendo è concorrenza, non parallelismo.Maggiori informazioni su questo here.

tua direzione è buona, anche se alcuni piccoli cambiamenti che avrei fatto:

In primo luogo, si dovrebbe contrassegnare il metodo di come async Task come si sta utilizzando Task.WhenAll, che restituisce un awaitable, che è necessario in modo asincrono aspettare. Successivamente, puoi semplicemente restituire l'operazione da PostAsJsonAsync, invece di attendere ogni chiamata all'interno del tuo Select. Ciò farà risparmiare un po 'di spese generali in quanto non genererà lo stato-macchina per la chiamata asincrona:

public async Task DeployViewAsync(int itemId, string itemCode, int environmentTypeId) 
{ 
    using (var client = new HttpClient()) 
    { 
     client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ApiUrl"]); 
     client.DefaultRequestHeaders.Accept.Clear(); 
     client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json")); 

     var agents = _agentRepository.GetAgentsByitemId(itemId); 
     var agentTasks = agents.Select(a => 
     { 
      var viewPostRequest = new 
      { 
       AgentId = a.AgentId, 
       itemCode = itemCode, 
       EnvironmentId = environmentTypeId 
      }; 

      return client.PostAsJsonAsync("api/postView", viewPostRequest); 
     }); 

     await Task.WhenAll(agentTasks); 
    } 
} 

HttpClient è in grado di fare richieste simultanee (vedi link @usr per più), quindi I don' Vedo un motivo per creare una nuova istanza ogni volta all'interno del tuo lambda. Tieni presente che se consumi più volte DeployViewAsync, forse vorresti mantenere il tuo HttpClient invece di assegnarne uno ogni volta e smaltirlo quando non avrai più bisogno dei suoi servizi.

4

HttpClient appears to be usable for concurrent requests. Non ho verificato personalmente questo, questo è solo quello che raccolgo dalla ricerca. Pertanto, non è necessario creare un nuovo client per ogni attività che si sta avviando. Puoi fare ciò che è più conveniente per te.

In generale, mi sforzo di condividere il più piccolo (mutevole) stato possibile. Le acquisizioni di risorse dovrebbero generalmente essere spinte verso l'interno verso il loro utilizzo. Penso che sia meglio creare uno helper CreateHttpClient e creare qui un nuovo client per ogni richiesta. Considerare la possibilità di creare il corpo Select un nuovo metodo asincrono. Quindi, l'utilizzo di HttpClient è completamente nascosto da DeployView.

Non dimenticare di await l'attività WhenAll ed eseguire il metodo async Task. (Se non si capisce il motivo per cui ciò che è necessario che hai qualche ricerca su await fare.)

+0

Grazie per la vostra risposta perspicace, come @YuvalItzchakov ho capito, per me ha più senso mantenere in questo caso il client, dato che è utilizzabile per richieste concorrenti, poiché questo metodo essere chiamato più volte – mitomed

6

solito non v'è alcuna necessità di parallelizzare le richieste - un thread fare richieste asincrone dovrebbe essere sufficiente (anche se si avere centinaia di richieste). Considerate questo codice:

var tasks = agents.Select(a => 
     { 
      var viewPostRequest = new 
       { 
        AgentId = a.AgentId, 
        itemCode = itemCode, 
        EnvironmentId = environmentTypeId 
       }; 

      return client.PostAsJsonAsync("api/postView", viewPostRequest); 
     }); 
    //now tasks is IEnumerable<Task<WebResponse>> 
    await Task.WhenAll(tasks); 
    //now all the responses are available 
    foreach(WebResponse response in tasks.Select(p=> p.Result)) 
    { 
     //do something with the response 
    } 

Tuttavia, è possibile sfruttare il parallelismo durante l'elaborazione delle risposte. Invece di quanto sopra loop 'foreach' è possibile utilizzare:

Parallel.Foreach(tasks.Select(p=> p.Result), response => ProcessResponse(response)); 

Ma TMO, questo è il migliore utilizzo delle asincrona e parallelismo:

var tasks = agents.Select(async a => 
     { 
      var viewPostRequest = new 
       { 
        AgentId = a.AgentId, 
        itemCode = itemCode, 
        EnvironmentId = environmentTypeId 
       }; 

      var response = await client.PostAsJsonAsync("api/postView", viewPostRequest); 
      ProcessResponse(response); 
     }); 
await Task.WhenAll(tasks); 

C'è una grande differenza tra i primi e gli ultimi esempi : Nel primo, si ha un thread che avvia richieste asincrone, attende (non bloccando) per tutti gli di restituirli e solo successivamente li elabora. Nel secondo esempio, si allega una continuazione a ciascuna attività. In questo modo, ogni risposta viene elaborata non appena arriva. Supponendo che l'attuale TaskScheduler consenta l'esecuzione parallela (multithread) di Task, nessuna risposta rimane inattiva come nel primo esempio.

* Modifica - se si do decidere di farlo parallelo, è possibile utilizzare solo un'istanza di HttpClient - è thread-safe.

+0

Async IO non rende più veloce l'IO. Non è correlato alla velocità di quel singolo IO. Può * solo * andare più veloce parallelizzando. – usr

+0

Non ho mai detto che async rende più veloce l'IO. Pubblicare una singola richiesta asincrona è relativamente veloce (non c'è bisogno di aspettare una risposta), abbastanza veloce da far sì che un thread ne faccia migliaia in pochissimo tempo. Ecco perché ho sottolineato "Di solito". –