Как ограничить максимальное количество параллельных задач в С#

У меня есть коллекция из 1000 входных сообщений для обработки. Я зациклирую коллекцию ввода и начинаю новую задачу, чтобы каждое сообщение обрабатывалось.

//Assume this messages collection contains 1000 items
var messages = new List<string>();

foreach (var msg in messages)
{
   Task.Factory.StartNew(() =>
   {
    Process(msg);
   });
 }

Можем ли мы догадаться, сколько максимальных сообщений одновременно обрабатывается в то время (при условии нормального четырехъядерного процессора), или мы можем ограничить максимальное количество сообщений, которые будут обрабатываться в то время?

Как обеспечить, чтобы это сообщение обрабатывалось в той же последовательности/порядке коллекции?

Ответ 1

SemaphoreSlim является очень хорошим решением в этом случае, и я настоятельно рекомендую OP, чтобы попробовать это, но ответ @Manoj имеет недостаток, как упомянуто в комментариях. Перед тем, как создать эту задачу, следует дождаться появления семафора.

Обновленный ответ: Как отметил @Vasyl, семафор может быть удален до завершения задач и вызовет исключение при вызове метода Release(), поэтому перед выходом из блока using необходимо дождаться завершения всех созданных задач.

int maxConcurrency=10;
var messages = new List<string>();
using(SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxConcurrency))
{
    List<Task> tasks = new List<Task>();
    foreach(var msg in messages)
    {
        concurrencySemaphore.Wait();

        var t = Task.Factory.StartNew(() =>
        {

            try
            {
                 Process(msg);
            }
            finally
            {
                concurrencySemaphore.Release();
            }
        });

        tasks.Add(t);
    }

    Task.WaitAll(tasks.ToArray());
}

Ответьте на Комментарии для тех, кто хочет увидеть, как семафор может быть расположен без Task.WaitAll Запустите приведенный ниже код в консольном приложении, и это исключение будет Task.WaitAll.

System.ObjectDisposedException: "Семафор удален".

static void Main(string[] args)
        {
            int maxConcurrency = 5;
            List<string> messages =  Enumerable.Range(1, 15).Select(e => e.ToString()).ToList();

            using (SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxConcurrency))
            {
                List<Task> tasks = new List<Task>();
                foreach (var msg in messages)
                {
                    concurrencySemaphore.Wait();

                    var t = Task.Factory.StartNew(() =>
                    {

                        try
                        {
                            Process(msg);
                        }
                        finally
                        {
                            concurrencySemaphore.Release();
                        }
                    });

                    tasks.Add(t);
                }

               // Task.WaitAll(tasks.ToArray());
            }
            Console.WriteLine("Exited using block");

            Console.ReadKey();
        }

        private static void Process(string msg)
        {            
            Thread.Sleep(2000);
            Console.WriteLine(msg);

        }
    }

Ответ 2

Вы можете использовать Parallel.Foreach и полагаться на MaxDegreeOfParallelism.

Parallel.ForEach(messages, new ParallelOptions {MaxDegreeOfParallelism = 10},
msg =>
{
     // logic
     Process(msg);
});

Ответ 3

думаю, будет лучше для пользователя Parallel LINQ

  Parallel.ForEach(messages ,
     new ParallelOptions{MaxDegreeOfParallelism = 4},
            x => Process(x);
        );

где x - максимальная степень Parallelism

Ответ 4

Вы можете просто установить максимальную степень concurrency следующим образом:

int maxConcurrency=10;
var messages = new List<1000>();
using(SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxConcurrency))
{
    foreach(var msg in messages)
    {
        Task.Factory.StartNew(() =>
        {
            concurrencySemaphore.Wait();
            try
            {
                 Process(msg);
            }
            finally
            {
                concurrencySemaphore.Release();
            }
        });
    }
}

Ответ 6

Если вам нужна очередь в порядке (обработка может завершиться в любом порядке), нет необходимости в семафоре. Старомодные, если утверждения работают нормально:

        const int maxConcurrency = 5;
        List<Task> tasks = new List<Task>();
        foreach (var arg in args)
        {
            var t = Task.Run(() => { Process(arg); } );

            tasks.Add(t);

            if(tasks.Count >= maxConcurrency)
                Task.WaitAny(tasks.ToArray());
        }

        Task.WaitAll(tasks.ToArray());

Ответ 7

 public static void RunTasks(List<NamedTask> importTaskList)
    {
        List<NamedTask> runningTasks = new List<NamedTask>();

        try
        {
            foreach (NamedTask currentTask in importTaskList)
            {
                currentTask.Start();
                runningTasks.Add(currentTask);

                if (runningTasks.Where(x => x.Status == TaskStatus.Running).Count() >= MaxCountImportThread)
                {
                    Task.WaitAny(runningTasks.ToArray());
                }
            }

            Task.WaitAll(runningTasks.ToArray());
        }
        catch (Exception ex)
        {
            Log.Fatal("ERROR!", ex);
        }
    }