Виклик декількох служб асинхронізації паралельно


17

У мене є кілька служб async REST, які не залежать один від одного. Тобто під час "очікування" відповіді від Service1 я можу зателефонувати до Service2, Service3 тощо.

Наприклад, зверніться до коду нижче:

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

// Use service1Response and service2Response

Тепер service2Responseце не залежить від їх, service1Responseі їх можна отримати самостійно. Отже, немає потреби чекати відповіді першої служби, щоб зателефонувати на другу службу.

Я не думаю, що я можу Parallel.ForEachтут використовувати, оскільки це не пов'язана з процесором операція.

Для того, щоб викликати ці дві операції паралельно, чи можу я зателефонувати на використання Task.WhenAll? Одне питання, яке я бачу, використовуючи Task.WhenAllте, що воно не дає результатів. Щоб отримати результат, я можу зателефонувати task.Resultпісля дзвінка Task.WhenAll, оскільки всі завдання вже виконані, і все, що мені потрібно для отримання відповіді?

Приклад коду:

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

await Task.WhenAll(task1, task2)

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

// Use result1 and result2

Чи кращий цей код, ніж перший за рівнем продуктивності? Будь-який інший підхід я можу використовувати?


I do not think I can use Parallel.ForEach here since it is not CPU bound operation- Я не бачу логіки там. Паралельність - це паралельність.
Роберт Харві

3
@RobertHarvey Я здогадуюсь, що стурбованість полягає в тому, що в цьому контексті Parallel.ForEachпороджуватимуться нові нитки, тоді як вони async awaitбудуть робити все на одній темі.
MetaFight

@Ankit це залежить від того, коли підходить ваш код блокувати. Ваш другий приклад буде заблокований, поки не будуть готові обидва відповіді. Ваш перший приклад, мабуть, блокується логічно лише тоді, коли код спробує використати відповідь ( await) перед її готовністю.
MetaFight

Можливо, буде простіше дати вам більш задоволену відповідь, якби ви подали менш абстрактний приклад коду, що використовує обидва сервісні відповіді.
MetaFight

@MetaFight У своєму другому прикладі я роблю, WhenAllперш ніж зайнятися Resultідеєю, що вона виконує всі завдання раніше. Оскільки Task.Result блокує викликову нитку, я припускаю, що якщо я викличу її після того, як завдання фактично виконані, він негайно поверне результат. Я хочу підтвердити розуміння.
Анкіт Віджай

Відповіді:


17

Одне питання я бачу за допомогою Task.WhenAll в тому, що він не повертає результатів

Але це дійсно повертає результати. Усі вони будуть знаходитись у масиві загального типу, тому не завжди корисно використовувати результати в тому, що вам потрібно знайти елемент у масиві, який відповідає тому, для Taskчого потрібно результат, та потенційно передати його його фактичний тип, тож це може бути не найпростішим / найчитабельнішим підходом у цьому контексті, але коли ви просто хочете мати всі результати від кожного завдання, а загальним типом є тип, до якого ви хочете ставитися до них, тоді це чудово .

Щоб отримати результат, я можу викликати task.Result після виклику Task.WhenAll, оскільки всі завдання вже виконані, і все, що мені потрібно для отримання відповіді?

Так, ви могли це зробити. Ви також можете awaitїх ( awaitрозгорнув би виняток у будь-якому несправному завданні, тоді як Resultвикинув би сукупний виняток, але в іншому випадку це було б саме).

Чи кращий цей код, ніж перший за рівнем продуктивності?

Він виконує дві операції одночасно, а не одну, а потім іншу. Будь це краще чи гірше, залежить від того, що є основними операціями. Якщо основними операціями є "зчитування файлу з диска", то виконання їх паралельно, ймовірно, повільніше, оскільки є лише одна головка диска, і вона може бути в одному місці в будь-який момент часу; перестрибування між двома файлами буде повільніше, ніж читання одного файлу, а потім іншого. З іншого боку, якщо операції "виконують певний мережевий запит" (як це відбувається тут), вони, швидше за все, будуть швидшими (принаймні, до певної кількості одночасних запитів), тому що ви можете чекати відповіді з іншого мережевого комп'ютера так само швидко, коли відбувається ще якийсь очікуваний мережевий запит. Якщо ви хочете знати, чи це "

Будь-який інший підхід я можу використовувати?

Якщо вам не важливо, що ви знаєте всі винятки, викинуті з усіх операцій, які ви робите паралельно, а не лише першу, ви можете просто awaitвиконати завдання WhenAll. Єдине, що WhenAllдає тобі - це те, що ти маєш AggregateExceptionвиняток із кожного невдалого завдання, а не кидати, коли ти потрапляєш у перше несправне завдання. Це так просто, як:

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

var result1 = await task1;
var result2 = await task2;

Це не виконання завдань одночасно, а не паралельно. Ви чекаєте виконання кожного завдання в послідовному порядку. Цілком добре, якщо вам не байдуже код виконавця.
Rick O'Shea

3
@ RickO'Shea Починає операції послідовно. Він почне другу операцію після того, як * запустить першу операцію. Але запуск асинхронної операції повинен бути в основному миттєвим (якщо це не так, насправді це не асинхронно, і це помилка в цьому методі). Після запуску однієї, а потім другої триває, поки не закінчиться перша, а потім друга. Оскільки нічого не чекає, коли перший закінчиться перед початком другого, ніщо не заважає їм працювати одночасно (що таке саме, як вони працюють паралельно).
Сервіс

@ Серві, я не думаю, що це правда. Я додав журнал всередині двох операцій з асинхронізацією, які займали близько однієї секунди (обидва роблять http-дзвінки), а потім викликав їх, як ви запропонували, і впевнений, що достатня задача1 почалася і закінчилася, а потім завдання2 почалася і закінчилася.
Метт

@MattFrear Тоді метод насправді не був асинхронним. Це було синхронно. За визначенням , асинхронний метод повернеться одразу, а не повертається після того, як операція фактично закінчилася.
Сервіс

@ Сервіс за визначенням, чекання означатиме, що ви зачекаєте, поки асинхронне завдання закінчиться перед виконанням наступного рядка. Чи не так?
Метт

0

Ось метод розширення, який використовує SemaphoreSlim і дозволяє встановити максимальний ступінь паралелізму

    /// <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)));
        }
    }

Використання зразка:

await enumerable.ForEachAsyncConcurrent(
    async item =>
    {
        await SomeAsyncMethod(item);
    },
    5);

-2

Можна або використовувати

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

або

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();

Чому голоси?
користувач1451111
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.