Подтвердить что ты не робот

У вас есть набор задач с одновременным запуском X

Скажем, у меня есть 100 задач, которые делают что-то, что занимает 10 секунд. Теперь я хочу запускать только 10 за один раз, когда 1 из этих 10 заканчивает выполнение другой задачи, пока все не закончится.

Теперь я всегда использовал ThreadPool.QueueUserWorkItem() для такой задачи, но я читал, что это плохая практика, и что вместо этого я должен использовать Задачи.

Моя проблема в том, что я нигде не нашел хорошего примера для моего сценария, так что вы могли бы начать меня с того, как достичь этой цели с помощью Задачи?

4b9b3361

Ответ 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
});