Llamar a múltiples servicios asíncronos en paralelo

14

Tengo pocos servicios REST asíncronos que no dependen unos de otros. Es decir, mientras "estoy esperando" una respuesta de Service1, puedo llamar a Service2, a Service3 y así sucesivamente.

Por ejemplo, consulte el siguiente código:

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

// Use service1Response and service2Response

Ahora, service2Response no depende de service1Response y se pueden buscar de forma independiente. Por lo tanto, no es necesario que espere la respuesta del primer servicio para llamar al segundo servicio.

No creo que pueda usar Parallel.ForEach aquí ya que no es una operación vinculada a la CPU.

Para llamar a estas dos operaciones en paralelo, ¿puedo llamar al uso Task.WhenAll ? Un problema que veo usando Task.WhenAll es que no devuelve resultados. Para obtener el resultado, ¿puedo llamar a task.Result después de llamar a Task.WhenAll , ya que todas las tareas ya están completadas y todo lo que necesito para obtener la respuesta?

Código de muestra:

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

await Task.WhenAll(task1, task2)

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

// Use result1 and result2

¿Es este código mejor que el primero en términos de rendimiento? ¿Cualquier otro enfoque que pueda usar?

    
pregunta Ankit Vijay 06.01.2017 - 03:46

3 respuestas

13
  

Un problema que veo usando Task.WhenAll es que no devuelve resultados

Pero devuelve los resultados. Todos estarán en una matriz de un tipo común, por lo que no siempre es útil utilizar los resultados porque necesita encontrar el elemento en la matriz que corresponde al Task que desea el resultado para, y potencialmente convertirlo en su tipo real, por lo que podría no ser el enfoque más fácil / más legible en este contexto, pero cuando solo desea obtener todos los resultados de cada tarea, y el tipo común es el tipo quieres tratarlos como, entonces es genial.

  

Para obtener el resultado, puedo llamar a la tarea.Resultar después de llamar a la tarea.¿Cuándo, ya que todas las tareas ya están completadas y todo lo que necesito para obtener la respuesta?

Sí, podrías hacer eso. También podría await them ( await desenvolvería la excepción en cualquier tarea con fallas, mientras que Result lanzaría una excepción agregada, pero de lo contrario sería lo mismo).

  

¿Es este código mejor que el primero en términos de rendimiento?

Realiza las dos operaciones al mismo tiempo, en lugar de una y luego la otra. Que sea mejor o peor depende de cuáles sean esas operaciones subyacentes. Si las operaciones subyacentes son "leer un archivo del disco", es probable que realizarlas en paralelo sea más lento, ya que solo hay una cabeza de disco y solo puede estar en un lugar en un momento dado; saltar entre dos archivos será más lento que leer un archivo y luego otro. Por otro lado, si las operaciones son "realizar alguna solicitud de red" (como es el caso aquí) entonces es muy probable que sean más rápidas (al menos hasta un cierto número de solicitudes concurrentes), porque puede esperar una respuesta desde otra computadora de la red igual de rápido cuando también hay otras solicitudes de red pendientes. Si desea saber si es más rápido en su situación, pruébelo.

  

¿Cualquier otro enfoque que pueda usar?

Si no es importante para usted que sepa todas de las excepciones generadas entre todas las operaciones que realiza en paralelo en lugar de la primera, puede simplemente await las tareas sin WhenAll en absoluto. Lo único que WhenAll te da es tener un AggregateException con cada excepción de cada tarea con fallas, en lugar de lanzar cuando golpeas la primera tarea con fallas. Es tan simple como:

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

var result1 = await task1;
var result2 = await task2;
    
respondido por el Servy 06.01.2017 - 17:41
0

Este es el método de extensión que utiliza SemaphoreSlim y permite establecer el máximo grado de paralelismo

    /// <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 de muestra:

await enumerable.ForEachAsyncConcurrent(
    async item =>
    {
        await SomeAsyncMethod(item);
    },
    5);
    
respondido por el Jay Shah 10.05.2018 - 00:49
-2

Puedes usar

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();
    
respondido por el user1451111 17.07.2018 - 15:43

Lea otras preguntas en las etiquetas