После небольшого обсуждения этой проблемы с другими пользователями, которые обеспокоены переключением контекста и его влиянием на производительность.
Я вижу, о чем они беспокоятся.
Но я имел в виду: сделать что-то ... внутри цикла, чтобы быть существенной задачей - обычно в форме обработчика сообщений, который читает сообщение из очереди и обрабатывает его. Обработчики сообщений обычно определяются пользователем, и шина сообщений выполняет их с помощью своего рода диспетчера. Пользователь может реализовать обработчик, который выполняется синхронно (никто не знает, что будет делать пользователь), и без 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);
}