Perché dovrei creare operazioni WebAPI asincrone invece di sincronizzare quelle?


109

Ho la seguente operazione in un'API Web che ho creato:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public CartTotalsDTO GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh);
}

La chiamata a questo servizio web viene effettuata tramite una chiamata Ajax Jquery in questo modo:

$.ajax({
      url: "/api/products/pharmacies/<%# Farmacia.PrimaryKeyId.Value.ToString() %>/page/" + vm.currentPage() + "/" + filter,
      type: "GET",
      dataType: "json",
      success: function (result) {
          vm.items([]);
          var data = result.Products;
          vm.totalUnits(result.TotalUnits);
      }          
  });

Ho visto alcuni sviluppatori che implementano l'operazione precedente in questo modo:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return await Task.Factory.StartNew(() => delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh));
}

Devo dire, però, che GetProductsWithHistory () è un'operazione piuttosto lunga. Dato il mio problema e il contesto, in che modo mi avvantaggia rendere asincrona l'operazione webAPI?


1
Il lato client utilizza AJAX, che è già asincrono. Non è necessario che il servizio sia scritto anche come file async Task<T>. Ricorda, AJAX è stato implementato prima ancora che esistesse il TPL :)
Dominic Zukiewicz

65
Devi capire perché stai implementando controller asincroni, molti no. IIS ha un numero limitato di thread disponibili e quando tutti sono in uso il server non può elaborare nuove richieste. Con i controller asincroni quando un processo è in attesa del completamento dell'I / O, il suo thread viene liberato per essere utilizzato dal server per l'elaborazione di altre richieste.
Matija Grcic

3
Quali sviluppatori hai visto farlo? Se c'è un post sul blog o un articolo che consiglia questa tecnica, inserisci un link.
Stephen Cleary

3
Ottieni tutti i vantaggi dell'asincronia solo se il tuo processo è consapevole dell'asincronia dall'alto (inclusa l'applicazione Web stessa e i controller) fino a qualsiasi attività in attesa che esula dal processo (inclusi ritardi del timer, I / O file, accesso DB, e richieste web che fa). In questo caso, l'aiutante delegato deve GetProductsWithHistoryAsync()essere restituito Task<CartTotalsDTO>. Può esserci un vantaggio nello scrivere il tuo controller come asincrono se intendi migrare anche le chiamate che fa per essere asincrono; quindi inizi a trarre vantaggio dalle parti asincrone mentre migra il resto.
Keith Robertson

1
Se il processo che stai facendo sta andando fuori e colpendo il database, il tuo thread web sta solo aspettando che torni e trattiene quel thread. Se hai raggiunto il numero massimo di thread e arriva un'altra richiesta, deve aspettare. Perché farlo? Invece vorresti liberare quel thread dal tuo controller in modo che un'altra richiesta possa usarlo e occupare un altro thread web solo quando la tua richiesta originale dal database è tornata. msdn.microsoft.com/en-us/magazine/dn802603.aspx
user441521

Risposte:


98

Nel tuo esempio specifico, l'operazione non è affatto asincrona, quindi ciò che stai facendo è asincrono su sincronizzazione. Stai solo rilasciando un thread e bloccandone un altro. Non c'è motivo per questo, perché tutti i thread sono thread del pool di thread (a differenza di un'applicazione GUI).

Nella mia discussione su "async over sync", ho fortemente suggerito che se si dispone di un'API implementata internamente in modo sincrono, non si dovrebbe esporre una controparte asincrona che avvolge semplicemente il metodo sincrono Task.Run.

A partire dal Devo esporre wrapper sincroni per metodi asincroni?

Tuttavia, quando si effettuano chiamate WebAPI async cui è presente un'effettiva operazione asincrona (di solito I / O) invece di bloccare un thread che si trova e attende un risultato, il thread torna al pool di thread e quindi in grado di eseguire qualche altra operazione. Tutto ciò significa che la tua applicazione può fare di più con meno risorse e questo migliora la scalabilità.


3
@efaruk tutti i thread sono thread di lavoro. Rilasciare un thread di ThreadPool e bloccarne un altro è inutile.
i3arnon

1
@efaruk Non sono sicuro di quello che stai cercando di dire .. ma fintanto che sei d'accordo non c'è motivo di usare async su sync in WebAPI, allora va bene.
i3arnon

@efaruk "async over sync" (cioè await Task.Run(() => CPUIntensive())) è inutile in asp.net. Non ci guadagni nulla. Stai solo rilasciando un thread ThreadPool per occuparne un altro. È meno efficiente rispetto alla semplice chiamata del metodo sincrono.
i3arnon

1
@efaruk No, non è ragionevole. L'esempio esegue le attività indipendenti in sequenza. Hai davvero bisogno di leggere su asyc / attendere prima di fare raccomandazioni. Dovresti usare await Task.WhenAllper eseguire in parallelo.
Søren Boisen

1
@efaruk Come spiega Boisen, il tuo esempio non aggiunge alcun valore oltre alla semplice chiamata sequenziale di questi metodi sincroni. È possibile utilizzarlo Task.Runse si desidera parallelizzare il carico su più thread, ma non è questo il significato di "async over sync". "async over sync" fa riferimento alla creazione di un metodo asincrono come wrapper su uno sincrono. Puoi vedere la citazione nella mia risposta.
i3arnon

1

Un approccio potrebbe essere (l'ho usato con successo nelle applicazioni dei clienti) avere un servizio Windows che esegue le lunghe operazioni con i thread di lavoro, quindi farlo in IIS per liberare i thread fino al completamento dell'operazione di blocco: Nota, questo presume i risultati vengono memorizzati in una tabella (righe identificate da jobId) e un processo più pulito li ripulisce alcune ore dopo l'uso.

Per rispondere alla domanda: "Dato il mio problema e il contesto, in che modo mi avvantaggia rendere asincrona l'operazione webAPI?" dato che è "un'operazione piuttosto lunga" penso a molti secondi anziché a ms, questo approccio libera i thread IIS. Ovviamente devi anche eseguire un servizio Windows che a sua volta richiede risorse, ma questo approccio potrebbe impedire a un flusso di query lente di rubare thread da altre parti del sistema.

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
        var jobID = Guid.NewGuid().ToString()
        var job = new Job
        {
            Id = jobId,
            jobType = "GetProductsWithHistory",
            pharmacyId = pharmacyId,
            page = page,
            filter = filter,
            Created = DateTime.UtcNow,
            Started = null,
            Finished = null,
            User =  {{extract user id in the normal way}}
        };
        jobService.CreateJob(job);

        var timeout = 10*60*1000; //10 minutes
        Stopwatch sw = new Stopwatch();
        sw.Start();
        bool responseReceived = false;
        do
        {
            //wait for the windows service to process the job and build the results in the results table
            if (jobService.GetJob(jobId).Finished == null)
            {
                if (sw.ElapsedMilliseconds > timeout ) throw new TimeoutException();
                await Task.Delay(2000);
            }
            else
            {
                responseReceived = true;
            }
        } while (responseReceived == false);

    //this fetches the results from the temporary results table
    return jobService.GetProductsWithHistory(jobId);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.