web-dev-qa-db-fra.com

Comment limiter le nombre maximum de tâches parallèles en c #

J'ai une collection de 1000 messages d'entrée à traiter. Je boucle la collection d'entrée et lance la nouvelle tâche pour que chaque message soit traité.

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

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

Pouvons-nous deviner combien de messages maximum sont traités simultanément à la fois (en supposant un processeur Quad Core normal), ou pouvons-nous limiter le nombre maximum de messages à traiter à la fois? 

Comment faire en sorte que ce message soit traité dans le même ordre/séquence de la collection?

20
Mathiyazhagan

SemaphoreSlim est une très bonne solution dans ce cas et je recommande vivement à OP d’essayer, mais la réponse de @ Manoj a le défaut mentionné dans comments.semaphore doit être attendu avant de générer une telle tâche.

Réponse mise à jour: Comme @Vasyl l'a souligné, Semaphore peut être supprimé avant l'achèvement des tâches et déclenche une exception lorsque la méthode Release() est appelée. Avant de quitter le bloc using, vous devez attendre l'achèvement de toutes les tâches créées.

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());
}

Réponse aux commentaires Pour ceux qui veulent voir comment un sémaphore peut être éliminé sans Task.WaitAllExécuter en dessous du code dans l'application console et cette exception sera déclenchée.

System.ObjectDisposedException: 'Le sémaphore a été supprimé.'

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);
}
21
ClearLogic

Vous pouvez utiliser Parallel.Foreach et vous appuyer sur MaxDegreeOfParallelism à la place.

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

penser serait mieux pour utiliser Parallel LINQ

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

où x est le degré maximal de parallélisme

Vous pouvez simplement définir le degré de concurrence maximum comme suit:

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();
            }
        });
    }
}
2
error_handler

Si vous avez besoin d'une mise en file d'attente dans l'ordre (le traitement peut se terminer dans n'importe quel ordre), un sémaphore n'est pas nécessaire. Démodé si les déclarations fonctionnent bien:

        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());
0
Neil Hunt

Vous pouvez créer votre propre TaskScheduler et remplacer la QueueTask ici.

protected virtual void QueueTask(Task task)

Ensuite, vous pouvez faire ce que vous voulez.

Un exemple ici:

Planificateur de tâches limité au niveau de la simultanéité (avec priorité de tâche) traitant des tâches encapsulées

0
Serve Laurijssen
 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);
        }
    }
0
Daniel