2013-03-31 2 views
0

J'ai un programme C# qui met en file d'attente un ensemble d'éléments de travail. Pour chaque élément de travail, je génère une autre application (que je ne peux pas modifier) ​​pour traiter cet élément. Ce processus est relativement gourmand en ressources processeur. Je voudrais limiter le nombre d'instances de cette application.Limitation de la simultanéité des processus de génération et d'attente des processus de travail

J'ai pensé à utiliser PLINQ:

Parallel.ForEach(
    workItems, 
    new ParallelOptions { MaxDegreeOfParallelism = 4 }, 
    x => Process.Start("worker", x).WaitForExit()); 

Mais ma préoccupation est que chaque travailleur parallèle utilisera simplement un thread pour attendre que le processus correspondant.

J'ai aussi vu que PLINQ essayait de grouper les articles retournés, ce qui signifie qu'il pourrait bloquer en attendant un lot de taille appropriée. Donc, peut-être un modèle mono-producteur/consommateur multiple utilisant BlockingCollection fonctionnerait? Le problème avec cela est que j'aurais un thread pour chaque travailleur parallèle, ce qui serait (?) Pire que la solution PLINQ. Etant donné que ce qui précède est simplifié et que j'ai effectivement un TaskCompletionSource connecté à chaque processus de travail (via l'événement Exited), est-ce que je pourrais utiliser quelque chose de la TPL pour faire cela sans bloquer les threads d'arrière-plan?

+0

À quel point le profilage de chaque thread dans le TPL était-il un problème? –

+0

Désolé: exercice académique. En fait bloquer avec 4 (ou 8) threads n'est pas réellement un problème, mais j'aimerais en savoir plus sur les options de planification et de concurrence dans TPL. –

+0

Pouvez-vous utiliser C# 5.0? – svick

Répondre

1

Si vous pouvez envelopper le début Process et terminer en utilisant l'événement Process.Exited dans un Task:

Task WrapExternalProcess(WorkItem workItem) { ... } 

Vous pouvez utiliser continuations pour éliminer le blocage total. Quelque chose comme cela ferait:

Task DoAllWork(IEnumerable<WorkItem> workItems) 
{ 
    int THREAD_COUNT = 4; 

    var bag = new ConcurrentBag<WorkItem>(workItems); 
    var ce = new CountdownEvent(THREAD_COUNT); 
    var tcs = new TaskCompletionSource<bool>(); 

    for (int i = 0 ; i < THREAD_COUNT ; i++) Work(bag, ce, tcs); 

    return tcs.Task; 
} 

void Work(
    ConcurrentBag<WorkItem> bag, 
    CountdownEvent ce, 
    TaskCompletionSource<bool> tcs 
) 
{ 
    WorkItem workItem; 
    if (bag.TryTake(out workItem)) 
    { 
     WrapExternalProcess(workItem) 
      .ContinueWith(t => Work(bag, ce, tcs)); 
    } 
    else // no more work 
    { 
     // If I'm the last thread to finish 
     if (ce.Signal()) tcs.SetResult(true); 
    } 
} 
+0

Le blocage de 'WaitForExit' n'est pas nécessaire: je suis abonné à l'événement' Process.Exited'. –

+0

@RogerLipscombe Vrai :) J'ai édité ma réponse. –

0

J'ai mis quelque chose comme ce qui suit:

var sem = new SemaphoreSlim(4); 
foreach (var item in workItems) 
{ 
    sem.Wait(); 

    ProcessAsync(item).ContinueWith(_ => sem.Release()); 
} 

Cela me donne un seul fil qui maintient un nombre limité de processus de fond en vol à la fois.

+0

Mais pourquoi avez-vous besoin de bloquer ce thread? Ne pourriez-vous pas faire la même chose sans bloquer aucun fil? – svick

+0

Dunno. Puis-je? C'est le plus proche de ne pas utiliser de threads. J'ai dû ajouter d'autres choses pour garder une trace du nombre d'éléments en suspens (puisqu'il laisse la boucle avec N <= 3 éléments restants), mais cela fonctionne. –

Questions connexes