Запуск asyn c функций параллельно из списка интерфейсов - PullRequest
0 голосов
/ 01 марта 2020

У меня есть вопрос similair к Запуск asyn c методов параллельно в том смысле, что я буду sh для запуска ряда функций из списка функций параллельно.

Я отметил, что в нескольких комментариях в Интернете упоминается, что если у вас есть другие методы ожидания в ваших методах, Task.WhenAll () не поможет, поскольку методы Asyn c не параллельны.

Затем я пошел дальше. и создал поток для каждого вызова функции с помощью приведенного ниже (количество параллельных функций обычно невелико: от 1 до 5):

public interface IChannel
{
    Task SendAsync(IMessage message);
}

public class SendingChannelCollection
{
    protected List<IChannel> _channels = new List<IChannel>();

    /* snip methods to add channels to list etc */

    public async Task SendAsync(IMessage message)
    {
        var tasks = SendAll(message);

        await Task.WhenAll(tasks.AsParallel().Select(async task => await task));
    }

    private IEnumerable<Task> SendAll(IMessage message)
    {
        foreach (var channel in _channels)
            yield return channel.SendAsync(message, qos);
    }
}

Я хотел бы проверить, что я не делаю ничего ужасного с кодом пахнет или жужжит, когда я понимаю, что я исправил из того, что нашел в Интернете. Большое спасибо заранее.

1 Ответ

2 голосов
/ 01 марта 2020

Давайте сравним поведение вашей строки:

await Task.WhenAll(tasks.AsParallel().Select(async task => await task));

в отличие от:

await Task.WhenAll(tasks);

Что вы делегируете PLINQ в первом случае? Только операция await, которая в принципе ничего не делает - она ​​вызывает механизм async / await для ожидания одного task. Таким образом, вы настраиваете запрос PLINQ, который выполняет всю тяжелую работу по разбиению и объединению результатов операции, которая составляет «ничего не делать, пока этот task не завершится». Я сомневаюсь, что это то, что вы хотите.

Если у вас есть другие методы ожидания в ваших методах, Task.WhenAll () не поможет, так как методы Asyn c не параллельны.

Я не смог найти это ни в одном из ответов на связанные вопросы, кроме одного комментария под самим вопросом. Я бы сказал, что это, вероятно, неправильное представление, вытекающее из того факта, что async / await волшебным образом не превращает ваш код в параллельный код. Но, предполагая, что вы находитесь в среде без пользовательского SynchronizationContext (т.е. не ASP или приложения WPF), продолжения функций async будут запланированы в пуле потоков и, возможно, будут выполняться параллельно. Я поручу вам этот ответ , чтобы пролить свет на это. По сути это означает, что если ваш SendAsync выглядит примерно так:

Task SendAsync(IMessage message)
{
    // Synchronous initialization code.

    await something;

    // Continuation code.
}

Тогда:

  • Первая часть до await выполняется синхронно. Если эта часть имеет большой вес, вы должны ввести параллелизм в SendAll, чтобы код инициализации выполнялся параллельно.
  • await работает как обычно, ожидая завершения работы без использования каких-либо потоков.
  • Код продолжения будет запланирован в пуле потоков, поэтому, если несколько await s fini sh одновременно работают, их продолжения могут выполняться параллельно , если В пуле потоков достаточно потоков.

Все вышеизложенное предполагает, что await something фактически ожидает асинхронно. Если есть вероятность, что await something завершится синхронно, то код продолжения также будет выполняться синхронно.

Теперь есть ловушка. В вопросе, который вы связали, одно из ответов гласит:

Task.WhenAll() имеет тенденцию выходить из строя с большим количеством задач, выполняемых одновременно - без модерации / регулирования.

Теперь я не знаю, правда ли это, поскольку я не смог найти другого источника, который бы заявлял об этом. Я предполагаю, что это возможно, и в этом случае на самом деле может быть полезно вызвать PLINQ, чтобы иметь дело с разделением и регулированием для вас. Однако вы сказали, что обычно обрабатываете 1-5 функций, поэтому вам не стоит об этом беспокоиться.

Итак, подведем итог: параллелизм сложен, и правильный подход зависит от того, как именно выглядит ваш метод SendAsync. Если он имеет тяжелый код инициализации и это то, что вы хотите распараллелить, вы должны выполнить все вызовы к SendAsync параллельно. В противном случае async / await все равно будет неявно использовать пул потоков, поэтому ваш вызов PLINQ является избыточным.

...