Многие люди думают, что вам нужно создать n потоков, если вы хотите обрабатывать n задачи параллельно. В большинстве случаев компьютер ожидает ввода-вывода, такого как сетевой трафик c, доступ к диску, передача памяти для вычислений GPU, аппаратное устройство для завершения операции и т. Д. c.
Учитывая это понимание, мы можем видеть, что жизнеспособным решением для параллельной обработки максимально возможного количества задач для данной аппаратной платформы является конвейерная работа: поместить работу в очередь и обработать ее, используя как можно больше потоков. Обычно это означает 1-2 потока на виртуальный процессор.
В C# мы можем выполнить sh это с помощью параллельной библиотеки задач (TPL):
class Program
{
static Task RunAsync(int x)
{
return Task.Delay(10000);
}
static async Task Main(string[] args)
{
var tasks = Enumerable.Range(0, 50000).Select(x => RunAsync());
Console.WriteLine("Waiting for tasks to complete...");
await Task.WhenAll(tasks);
Console.WriteLine("Done");
}
}
Это ставит в очередь 50000 рабочие элементы и ожидает завершения всех 50000 задач. Эти задачи выполняются только в необходимом количестве потоков. За кулисами планировщик задач исследует пул работы и имеет потоки крадут работу из очереди, когда им нужно выполнить задачу.
Дополнительные соображения
С большая верхняя граница (n = 50000), вам следует учитывать нехватку памяти, активность сборщика мусора и другие накладные расходы, связанные с задачами. Следует учитывать следующее:
- Рассмотрите возможность использования ValueTask , чтобы минимизировать выделения, особенно для синхронных операций
- Используйте ConfigureAwait (false) там, где это возможно, чтобы уменьшить переключение контекста
- Используйте CancellationTokenSource и CancellationToken для ранней отмены запросов (например, тайм-аут)
- Следуйте рекомендациям
- Избегайте ожидания внутри al oop, где это возможно
- Избегайте слишком частого запроса задач для завершения
- Избегайте доступа Task . Результат до того, как задача будет выполнена завершено для предотвращения блокировки
- Избегайте взаимоблокировок, используя примитивы синхронизации (мьютекс, семафор, сигнал условия, синхронизация и т. д. c) в зависимости от ситуации
- Избегайте частого использования Task.Run для создания задач, чтобы избежать исчерпания пула потоков, доступного для планировщика задач по умолчанию (этот метод обычно зарезервирован для задач с привязкой к вычислениям)