День 1412. #ЗаметкиНаПолях #AsyncTips
Выполнение Кода с Помощью Планировщика. Начало: Создание Планировщика
Задача
Есть несколько частей кода, которые требуется выполнить определённым способом. Например, все они должны выполняться в UI-потоке или же в любой момент времени должно выполняться только определённое количество частей.
Решение
Здесь рассмотрим тип TaskScheduler, хотя, в .NET есть и другие способы решения этой задачи.
Простейшая разновидность — TaskScheduler.Default — ставит работу в очередь пула потоков. Его редко придётся использовать явно, потому что он используется по умолчанию во многих сценариях планирования: в Task.Run, в параллельном коде и в коде потоков данных.
Вы можете сохранить конкретный контекст и позднее спланировать работу в этом контексте:
var scheduler =
TaskScheduler
.FromCurrentSynchronizationContext();
Этот код создаёт объект TaskScheduler, чтобы сохранить текущий контекст и спланировать выполнение кода в нём. Тип SynchronizationContext представляет контекст планирования общего назначения. В .NET предусмотрено несколько разных контекстов: многие UI-фреймворки предоставляют контекст SynchronizationContext, представляющий UI-поток, а в ASP.NET до Core предоставлялся контекст SynchronizationContext, представляющий контекст запроса HTTP. Также возможно напрямую использовать SynchronizationContext для выполнения кода в этом контексте; но это не рекомендуется. Там, где это возможно, используйте await для возобновления в неявно сохранённом контексте либо TaskScheduler.
ConcurrentExclusiveSchedulerPair — тип, представляющий в действительности два планировщика, связанных друг с другом:
- ConcurrentScheduler позволяет нескольким задачам выполняться одновременно, при условии, что ни одна задача не выполняется в ExclusiveScheduler;
- ExclusiveScheduler выполняет только по одной задаче за раз и только если в настоящее время никакие задачи не выполняются в ConcurrentScheduler.
var schPair = new ConcurrentExclusiveSchedulerPair();
var concurrent = schPair.ConcurrentScheduler;
var exclusive = schPair.ExclusiveScheduler;
Одно из частых применений этого типа — просто использование ExclusiveScheduler, гарантирующее, что в любой момент времени будет выполняться только одна задача. Код, выполняемый в ExclusiveScheduler, будет выполняться в пуле потоков эксклюзивно - без всего остального кода, использующего тот же экземпляр ExclusiveScheduler.
Также тип может применяться в качестве регулирующего планировщика, который будет ограничивать собственный уровень параллелизма. При этом ExclusiveScheduler обычно не используется:
var schPair = new ConcurrentExclusiveSchedulerPair(
TaskScheduler.Default,
maxConcurrencyLevel: 8);
var scheduler = schPair.ConcurrentScheduler;
Заметьте, что такая регулировка влияет на код только во время его выполнения. В частности, асинхронный код не считается выполняемым во время ожидания операции. ConcurrentScheduler регулирует выполняющийся код; тогда как другие виды регулировки (такие, как SemaphoreSlim) осуществляют регулировку на более высоком уровне (т.е. всего async-метода.)
Заметьте, что конструктору ConcurrentExclusiveSchedulerPair передаётся объект TaskScheduler.Default. Это объясняется тем, что ConcurrentExclusiveSchedulerPair применяет свою логику конкурентности/эксклюзивности выполнения к существующему TaskScheduler.
Никогда не используйте платформенно-зависимые типы для выполнения кода в UI-потоке. WPF, Silverlight, iOS и Android предоставляют тип Dispatcher, Universal Windows использует тип CoreDispatcher, а в Windows Forms существует интерфейс ISynchronizeInvoke (т. е. Control.Invoke). Не используйте эти типы в новом коде; просто считайте, что их вообще нет. Эти типы только без всякой необходимости привязывают код к конкретной платформе. SynchronizationContext — абстракция общего назначения на базе этих типов.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 13.