Haben Sie eine Reihe von Aufgaben mit nur X auf einmal ausgeführt

Nehmen wir an, ich habe 100 Aufgaben, die etwas tun, was 10 Sekunden dauert. Jetzt möchte ich nur 10 auf einmal laufen lassen, wie wenn eine dieser 10 endet, wird eine andere Aufgabe ausgeführt, bis alle fertig sind.

Jetzt habe ich immer ThreadPool.QueueUserWorkItem() für eine solche Aufgabe verwendet, aber ich habe gelesen, dass es eine schlechte Übung ist, dies zu tun, und dass ich stattdessen Tasks verwenden sollte.

Mein Problem ist, dass ich nirgendwo ein gutes Beispiel für mein Szenario gefunden habe. Kannst du mich also dazu bringen, wie ich dieses Ziel mit Tasks erreichen kann?

 SemaphoreSlim maxThread = new SemaphoreSlim(10); for (int i = 0; i < 115; i++) { maxThread.Wait(); Task.Factory.StartNew(() => { //Your Works } , TaskCreationOptions.LongRunning) .ContinueWith( (task) => maxThread.Release() ); } 

TPL Dataflow eignet sich hervorragend für solche Aufgaben . Sie können eine 100% Async-Version von Parallel.Invoke ganz einfach erstellen:

 async Task ProcessTenAtOnce(IEnumerable items, Func func) { ExecutionDataflowBlockOptions edfbo = new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 10 }; ActionBlock ab = new ActionBlock(func, edfbo); foreach (T item in items) { await ab.SendAsync(item); } ab.Complete(); await ab.Completion; } 

Sie haben mehrere Möglichkeiten. Sie können Parallel.Invoke für Starter verwenden:

 public void DoWork(IEnumerable actions) { Parallel.Invoke(new ParallelOptions() { MaxDegreeOfParallelism = 10 } , actions.ToArray()); } 

Hier ist eine alternative Option, die sehr viel härter arbeiten wird, um genau 10 Aufgaben auszuführen (obwohl die Anzahl der Threads im Threadpool, die diese Aufgaben verarbeiten, unterschiedlich sein kann) und die eine Task zurückgibt, die anzeigt, wann sie beendet wird, statt zu blockieren.

 public Task DoWork(IList actions) { List tasks = new List(); int numWorkers = 10; int batchSize = (int)Math.Ceiling(actions.Count / (double)numWorkers); foreach (var batch in actions.Batch(actions.Count / 10)) { tasks.Add(Task.Factory.StartNew(() => { foreach (var action in batch) { action(); } })); } return Task.WhenAll(tasks); } 

Wenn Sie für die Batch function nicht über MoreLinq verfügen, ist hier meine einfachere Implementierung:

 public static IEnumerable> Batch(this IEnumerable source, int batchSize) { List buffer = new List(batchSize); foreach (T item in source) { buffer.Add(item); if (buffer.Count >= batchSize) { yield return buffer; buffer = new List(); } } if (buffer.Count >= 0) { yield return buffer; } } 

Ich würde gerne die einfachste Lösung verwenden, die ich mir vorstellen kann, wenn ich an die TPL denke:

 string[] urls={}; Parallel.ForEach(urls, new ParallelOptions() { MaxDegreeOfParallelism = 2}, url => { //Download the content or do whatever you want with each URL }); 

Sie können eine Methode wie folgt erstellen:

 public static async Task RunLimitedNumberAtATime(int numberOfTasksConcurrent, IEnumerable inputList, Func asyncFunc) { Queue inputQueue = new Queue(inputList); List runningTasks = new List(numberOfTasksConcurrent); for (int i = 0; i < numberOfTasksConcurrent && inputQueue.Count > 0; i++) runningTasks.Add(asyncFunc(inputQueue.Dequeue())); while (inputQueue.Count > 0) { Task task = await Task.WhenAny(runningTasks); runningTasks.Remove(task); runningTasks.Add(asyncFunc(inputQueue.Dequeue())); } await Task.WhenAll(runningTasks); } 

Und dann kannst du jede asynchrone Methode n mal mit einem Limit wie diesem aufrufen:

 Task task = RunLimitedNumberAtATime(10, Enumerable.Range(1, 100), async x => { Console.WriteLine($"Starting task {x}"); await Task.Delay(100); Console.WriteLine($"Finishing task {x}"); }); 

Oder wenn Sie lange nicht asynchrone Methoden ausführen möchten, können Sie das folgendermaßen tun:

 Task task = RunLimitedNumberAtATime(10, Enumerable.Range(1, 100), x => Task.Factory.StartNew(() => { Console.WriteLine($"Starting task {x}"); System.Threading.Thread.Sleep(100); Console.WriteLine($"Finishing task {x}"); }, TaskCreationOptions.LongRunning)); 

Vielleicht gibt es irgendwo im Rahmen eine ähnliche Methode, aber ich habe sie noch nicht gefunden.