Chiamando più servizi asincroni in parallelo

14

Ho pochi servizi REST asincroni che non dipendono l'uno dall'altro. Cioè mentre "attendo" una risposta da Service1, posso chiamare Service2, Service3 e così via.

Ad esempio, fai riferimento al seguente codice:

var service1Response = await HttpService1Async();
var service2Response = await HttpService2Async();

// Use service1Response and service2Response

Ora, service2Response non dipende da service1Response e possono essere recuperati indipendentemente. Quindi, non c'è bisogno che io attenda la risposta del primo servizio per chiamare il secondo servizio.

Non penso di poter usare Parallel.ForEach qui poiché non è un'operazione legata alla CPU.

Per chiamare queste due operazioni in parallelo, posso chiamare use Task.WhenAll ? Un problema che vedo con Task.WhenAll è che non restituisce risultati. Per recuperare il risultato posso chiamare task.Result dopo aver chiamato Task.WhenAll , poiché tutte le attività sono già state completate e tutto ciò che devo recuperare è la risposta?

Codice di esempio:

var task1 = HttpService1Async();
var task2 = HttpService2Async();

await Task.WhenAll(task1, task2)

var result1 = task1.Result;
var result2 = task2.Result;

// Use result1 and result2

Questo codice è migliore del primo in termini di prestazioni? Qualche altro approccio che posso usare?

    
posta Ankit Vijay 06.01.2017 - 03:46
fonte

3 risposte

13

One issue I see using Task.WhenAll is that it does not return results

Ma restituisce i risultati. Saranno tutti in un array di tipo comune, quindi non è sempre utile utilizzare i risultati in quanto è necessario trovare l'elemento nell'array che corrisponde al Task che si desidera il risultato per, e potenzialmente lo lanci al suo tipo effettivo, quindi potrebbe non essere l'approccio più facile / più leggibile in questo contesto, ma quando vuoi solo avere tutti i risultati di ogni attività, e il tipo comune è il tipo vuoi trattarli come, allora è grandioso.

To fetch the result can I call task.Result after calling Task.WhenAll, since all tasks are already completed and all I need to fetch us response?

Sì, potresti farlo. Potresti anche await ( await scarterebbe l'eccezione in qualsiasi attività con errore, mentre Result genererebbe un'eccezione aggregata, ma altrimenti sarebbe la stessa).

Is this code better than the first one in terms of performance?

Esegue le due operazioni contemporaneamente, anziché una e poi l'altra. Che sia meglio o peggio dipende da quali sono le operazioni sottostanti. Se le operazioni sottostanti sono "leggere un file dal disco", allora eseguirle in parallelo è probabilmente più lento, in quanto vi è una sola testina del disco e può essere in un solo posto in un dato momento; saltare tra due file sarà più lento di leggere un file e poi un altro. D'altra parte, se le operazioni sono "eseguire alcune richieste di rete" (come nel caso qui), molto probabilmente saranno più veloci (almeno fino a un certo numero di richieste concorrenti), perché è possibile attendere una risposta da qualche altro computer di rete altrettanto veloce quando c'è anche qualche altra richiesta di rete in attesa. Se vuoi sapere se è più veloce nella tua situazione, testalo.

Any other approach I can use?

Se per te non è importante sapere tutte delle eccezioni generate tra tutte le operazioni che esegui in parallelo anziché solo la prima, puoi semplicemente await delle attività senza WhenAll . L'unica cosa che WhenAll ti dà sta avendo un AggregateException con ogni singola eccezione da ogni attività guasta, piuttosto che lanciare quando colpisci la prima operazione guasta. È semplice come:

var task1 = HttpService1Async();
var task2 = HttpService2Async();

var result1 = await task1;
var result2 = await task2;
    
risposta data 06.01.2017 - 17:41
fonte
0

Ecco il metodo di estensione che utilizza SemaphoreSlim e consente di impostare il massimo grado di parallelismo

    /// <summary>
    /// Concurrently Executes async actions for each item of <see cref="IEnumerable<typeparamref name="T"/>
    /// </summary>
    /// <typeparam name="T">Type of IEnumerable</typeparam>
    /// <param name="enumerable">instance of <see cref="IEnumerable<typeparamref name="T"/>"/></param>
    /// <param name="action">an async <see cref="Action" /> to execute</param>
    /// <param name="maxDegreeOfParallelism">Optional, An integer that represents the maximum degree of parallelism,
    /// Must be grater than 0</param>
    /// <returns>A Task representing an async operation</returns>
    /// <exception cref="ArgumentOutOfRangeException">If the maxActionsToRunInParallel is less than 1</exception>
    public static async Task ForEachAsyncConcurrent<T>(
        this IEnumerable<T> enumerable,
        Func<T, Task> action,
        int? maxDegreeOfParallelism = null)
    {
        if (maxDegreeOfParallelism.HasValue)
        {
            using (var semaphoreSlim = new SemaphoreSlim(
                maxDegreeOfParallelism.Value, maxDegreeOfParallelism.Value))
            {
                var tasksWithThrottler = new List<Task>();

                foreach (var item in enumerable)
                {
                    // Increment the number of currently running tasks and wait if they are more than limit.
                    await semaphoreSlim.WaitAsync();

                    tasksWithThrottler.Add(Task.Run(async () =>
                    {
                        await action(item).ContinueWith(res =>
                        {
                            // action is completed, so decrement the number of currently running tasks
                            semaphoreSlim.Release();
                        });
                    }));
                }

                // Wait for all tasks to complete.
                await Task.WhenAll(tasksWithThrottler.ToArray());
            }
        }
        else
        {
            await Task.WhenAll(enumerable.Select(item => action(item)));
        }
    }

Uso del campione:

await enumerable.ForEachAsyncConcurrent(
    async item =>
    {
        await SomeAsyncMethod(item);
    },
    5);
    
risposta data 10.05.2018 - 00:49
fonte
-2

Puoi utilizzare

Parallel.Invoke(() =>
{
    HttpService1Async();
},
() =>
{   
    HttpService2Async();
});

o

Task task1 = Task.Run(() => HttpService1Async());
Task task2 = Task.Run(() => HttpService2Async());

//If you wish, you can wait for a particular task to return here like this:
task1.Wait();
    
risposta data 17.07.2018 - 15:43
fonte

Leggi altre domande sui tag