Avere una serie di attività con solo X in esecuzione alla volta

Diciamo che ho 100 compiti che fanno qualcosa che richiede 10 secondi. Ora voglio correre solo 10 alla volta come quando 1 di quei 10 finisce un altro compito viene eseguito fino a quando tutti sono finiti.

Ora ho sempre usato ThreadPool.QueueUserWorkItem() per tale compito, ma ho letto che è una ctriggers pratica farlo e che dovrei usare invece Task.

Il mio problema è che non ho trovato un buon esempio per il mio scenario, quindi potresti iniziare a capire come raggiungere questo objective con Google Task?

 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 è ottimo per fare cose come questa. Puoi creare una versione asincrona al 100% di Parallel.Invoke abbastanza facilmente:

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

Hai diverse opzioni. Puoi utilizzare Parallel.Invoke per i principianti:

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

Ecco un’opzione alternativa che funziona molto più duramente per avere esattamente 10 attività in esecuzione (anche se il numero di thread nel pool di thread che elabora tali attività potrebbe essere diverso) e che restituisce un’attività che indica quando termina, piuttosto che bloccare fino al completamento.

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

Se non hai MoreLinq, per la funzione Batch , ecco la mia implementazione più semplice:

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

Mi piacerebbe usare la soluzione più semplice a cui possa pensare quale sia l’utilizzo della TPL:

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

Puoi creare un metodo come questo:

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

E poi puoi chiamare qualsiasi metodo asincrono n volte con un limite come questo:

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

O se vuoi eseguire metodi non asincroni a lunga esecuzione, puoi farlo in questo modo:

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

Forse c’è un metodo simile da qualche parte nel framework, ma non l’ho ancora trovato.