У вас есть набор задач с одновременным запуском X
Скажем, у меня есть 100 задач, которые делают что-то, что занимает 10 секунд.
Теперь я хочу запускать только 10 за один раз, когда 1 из этих 10 заканчивает выполнение другой задачи, пока все не закончится.
Теперь я всегда использовал ThreadPool.QueueUserWorkItem()
для такой задачи, но я читал, что это плохая практика, и что вместо этого я должен использовать Задачи.
Моя проблема в том, что я нигде не нашел хорошего примера для моего сценария, так что вы могли бы начать меня с того, как достичь этой цели с помощью Задачи?
Ответы
Ответ 1
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() );
}
Ответ 2
TPL Dataflow отлично подходит для подобных действий. Вы можете создать 100% асинхронную версию Parallel.Invoke
довольно легко:
async Task ProcessTenAtOnce<T>(IEnumerable<T> items, Func<T, Task> func)
{
ExecutionDataflowBlockOptions edfbo = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 10
};
ActionBlock<T> ab = new ActionBlock<T>(func, edfbo);
foreach (T item in items)
{
await ab.SendAsync(item);
}
ab.Complete();
await ab.Completion;
}
Ответ 3
У вас есть несколько вариантов. Вы можете использовать Parallel.Invoke
для стартеров:
public void DoWork(IEnumerable<Action> actions)
{
Parallel.Invoke(new ParallelOptions() { MaxDegreeOfParallelism = 10 }
, actions.ToArray());
}
Вот альтернативный вариант, который будет работать намного сложнее, если будет выполнено ровно 10 запусков (хотя количество потоков в пуле потоков, обрабатывающих эти задачи, может быть различным), и возвращает Task
, указывающий, когда он заканчивается, а не блокировка до завершения.
public Task DoWork(IList<Action> actions)
{
List<Task> tasks = new List<Task>();
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);
}
Если у вас нет MoreLinq, для функции Batch
, здесь моя более простая реализация:
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
{
List<T> buffer = new List<T>(batchSize);
foreach (T item in source)
{
buffer.Add(item);
if (buffer.Count >= batchSize)
{
yield return buffer;
buffer = new List<T>();
}
}
if (buffer.Count >= 0)
{
yield return buffer;
}
}
Ответ 4
Я хотел бы использовать самое простое решение, о котором я думаю, что, как я думаю, используя TPL:
string[] urls={};
Parallel.ForEach(urls, new ParallelOptions() { MaxDegreeOfParallelism = 2}, url =>
{
//Download the content or do whatever you want with each URL
});