Использование Task.Yield для преодоления истощения ThreadPool при реализации шаблона «производитель / потребитель» - PullRequest
0 голосов
/ 12 ноября 2018

Отвечая на вопрос: Task.Yield - реальные использования? Я предложил использовать Task.Yield, позволяющий использовать поток пула другими задачами. По такой схеме:

  CancellationTokenSource cts;
  void Start()
  {
        cts = new CancellationTokenSource();

        // run async operation
        var task = Task.Run(() => SomeWork(cts.Token), cts.Token);
        // wait for completion
        // after the completion handle the result/ cancellation/ errors
    }

    async Task<int> SomeWork(CancellationToken cancellationToken)
    {
        int result = 0;

        bool loopAgain = true;
        while (loopAgain)
        {
            // do something ... means a substantial work or a micro batch here - not processing a single byte

            loopAgain = /* check for loop end && */  cancellationToken.IsCancellationRequested;
            if (loopAgain) {
                // reschedule  the task to the threadpool and free this thread for other waiting tasks
                await Task.Yield();
            }
        }
        cancellationToken.ThrowIfCancellationRequested();
        return result;
    }

    void Cancel()
    {
        // request cancelation
        cts.Cancel();
    }

Но один пользователь написал

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

Кто-нибудь знает, почему это не очень хорошая идея?

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018

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

Но я имел в виду: сделать что-то ... внутри цикла, чтобы быть существенной задачей - обычно в форме обработчика сообщений, который читает сообщение из очереди и обрабатывает его. Обработчики сообщений обычно определяются пользователем, и шина сообщений выполняет их с помощью своего рода диспетчера. Пользователь может реализовать обработчик, который выполняется синхронно (никто не знает, что будет делать пользователь), и без Task.Yield, который будет блокировать поток для обработки этих синхронных задач в цикле.

Чтобы не быть пустым, я добавил тесты в github: https://github.com/BBGONE/TestThreadAffinity Они сравнивают ThreadAffinityTaskScheduler, .NET ThreadScheduler с BlockingCollection и .NET ThreadScheduler с Threading.Channels.

Испытания показывают, что для сверхкоротких работ снижение производительности около 15%. Использовать Task.Yield без снижения производительности (даже небольшого) - это не использовать чрезвычайно короткие задачи, а если задача слишком короткая, объединить более короткие задачи в больший пакет.

[Цена переключения контекста] = [продолжительность переключения контекста] / ([продолжительность задания] + [продолжительность переключения контекста]) .

В этом случае влияние переключения задач незначительно на производительность. Но это добавляет лучшую задачу сотрудничеству и отзывчивости системы.

Для долгосрочных задач лучше использовать собственный планировщик, который выполняет задачи в своем собственном выделенном пуле потоков (например, WorkStealingTaskScheduler).

Для смешанных заданий - которые могут содержать разные части - непродолжительные части с привязкой к ЦП, асинхронные и длительно работающие части кода. Лучше разбить задачу на подзадачи.

private async Task HandleLongRunMessage(TestMessage message, CancellationToken token = default(CancellationToken))
{ 
            // SHORT SYNCHRONOUS TASK - execute as is on the default thread (from thread pool)
            CPU_TASK(message, 50);
            // IO BOUND ASYNCH TASK - used as is
            await Task.Delay(50);
            // BUT WRAP the LONG SYNCHRONOUS TASK inside the Task 
            // which is scheduled on the custom thread pool 
            // (to save threadpool threads)
            await Task.Factory.StartNew(() => {
                CPU_TASK(message, 100000);
            }, token, TaskCreationOptions.DenyChildAttach, _workStealingTaskScheduler);
}
0 голосов
/ 13 ноября 2018

В комментариях к вашему вопросу осталось несколько хороших моментов. Будучи пользователем, которого вы цитировали, я просто хотел бы подвести итог: использовать правильный инструмент для работы.

Использование ThreadPool не похоже на правильный инструмент для выполнения нескольких непрерывных задач, связанных с ЦП, даже если вы пытаетесь организовать некоторое совместное выполнение, превратив их в конечные автоматы, которые отдают время ЦП друг другу с помощью await Task.Yield() , Переключение потоков довольно дорогое; выполняя await Task.Yield() в узком цикле, вы добавляете значительные издержки Кроме того, вы никогда не должны брать на себя весь ThreadPool, так как .NET Framework (и основной процесс ОС) может понадобиться для других целей. В связи с этим у TPL даже есть опция TaskCreationOptions.LongRunning, которая запрашивает не запускать задачу в потоке ThreadPool (скорее, он создает нормальный поток с new Thread() за сценой).

Тем не менее, использование custom TaskScheduler с ограниченным параллелизмом в некоторых выделенных потоках вне пула с привязкой потоков к отдельным долгосрочным задачам может быть другим вещь. По крайней мере, продолжения await будут размещены в одном и том же потоке, что должно помочь уменьшить накладные расходы на переключение. Это напоминает мне о другой проблеме, которую я пытался решить некоторое время назад с ThreadAffinityTaskScheduler.

Тем не менее, в зависимости от конкретного сценария, обычно лучше использовать существующий проверенный и проверенный инструмент. Чтобы назвать несколько: Параллельный класс , Поток данных TPL , System.Threading.Channels , Реактивные расширения .

Существует также целый ряд существующих отраслевых решений для работы с шаблоном публикации-подписки (RabbitMQ, PubNub, Redis, служебная шина Azure, облачная система обмена сообщениями Firebase (FCM), служба Amazon Simple Queue Service (SQS) и т. Д.).

...