Ограничение количества одновременно выполняемых задач

рассмотрим этот огромный пул задач:

var tasks = new Task[4]
    {
        Task.Factory.StartNew(() => DoSomething()),
        Task.Factory.StartNew(() => DoSomething()),
        Task.Factory.StartNew(() => DoSomething()),
        Task.Factory.StartNew(() => DoSomething()),
        Task.Factory.StartNew(() => DoSomething())
    };

Task.WaitAll(tasks);

что, если бы я хотел запустить только 3 задачи одновременно? Как бы я реализовал это в коде?

4 ответов


менее сложным примером, чем версия MSDN, будет использование Parallel.Вызовите установку максимальной степени параллелизма:

Parallel.Invoke(
    new ParallelOptions() { MaxDegreeOfParallelism = 3 }, 
    () => DoSomething(), 
    () => DoSomething(),
    () => DoSomething(),
    () => DoSomething(),
    () => DoSomething());

параллельный.Однако Invoke () будет блокировать до тех пор, пока все параллельные операции не будут завершены (что означает, что нет кода за пределами параллели.invoke будет выполняться до тех пор, пока все они не будут завершены). Если это не сработает для вас, вам придется создать свой собственный планировщик задач, как показано в статье MSDN, связанной с Daniel.


нашел на MSDN. Я считаю, что он реализует то, чего вы пытаетесь достичь.


Итак, вы хотите указать количество одновременных задач. Имейте в виду, что это плохая идея дизайна - по крайней мере, в большинстве случаев вы должны позволить системе решать, сколько задач выполнять одновременно. Когда вы используете Task.Factory.StartNew метод для создания задач таким образом без дополнительных параметров, они предназначены для выполнения как можно скорее (как можно скорее), поэтому вы обычно не должны явно указывать номер для их одновременного выполнения.

что означает ASAP в этом случае? Диспетчер задач решит, будет ли он запускать все из них немедленно, или jsut некоторые из них, или ждать завершения некоторых других задач и т. д.

Вы можете использовать какой-то ручной synchornization, чтобы достичь своей цели. Я имею в виду, как семафор. http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx

Если вам не нужно делать другую работу и хотите просто дождаться завершения задач, я бы предпочел Parallel.Invoke как suggeted Гари С.


мой блог показывает, как это сделать как с задачами, так и с действиями, и предоставляет пример проекта, который вы можете загрузить и запустить, чтобы увидеть оба в действии.

в оригинальном плакате не указано, предпочитают ли они использовать действия или задачи, и иногда нелегко переключаться с одного на другой, поэтому я представляю оба решения здесь.

Действиями

при использовании действий можно использовать встроенную параллель .Net.Вызвать функцию. Здесь мы ограничиваемся он работает не более 3 потоков параллельно.

var listOfActions = new List<Action>();
for (int i = 0; i < 10; i++)
{
    // Note that we create the Action here, but do not start it.
    listOfActions.Add(() => DoSomething());
}

var options = new ParallelOptions {MaxDegreeOfParallelism = 3};
Parallel.Invoke(options, listOfActions.ToArray());

Задачи

поскольку вы используете задачи здесь, хотя, нет встроенной функции. Тем не менее, вы можете использовать тот, который я предоставляю в своем блоге.

    /// <summary>
    /// Starts the given tasks and waits for them to complete. This will run, at most, the specified number of tasks in parallel.
    /// <para>NOTE: If one of the given tasks has already been started, an exception will be thrown.</para>
    /// </summary>
    /// <param name="tasksToRun">The tasks to run.</param>
    /// <param name="maxTasksToRunInParallel">The maximum number of tasks to run in parallel.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    public static void StartAndWaitAllThrottled(IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, CancellationToken cancellationToken = new CancellationToken())
    {
        StartAndWaitAllThrottled(tasksToRun, maxTasksToRunInParallel, -1, cancellationToken);
    }

    /// <summary>
    /// Starts the given tasks and waits for them to complete. This will run, at most, the specified number of tasks in parallel.
    /// <para>NOTE: If one of the given tasks has already been started, an exception will be thrown.</para>
    /// </summary>
    /// <param name="tasksToRun">The tasks to run.</param>
    /// <param name="maxTasksToRunInParallel">The maximum number of tasks to run in parallel.</param>
    /// <param name="timeoutInMilliseconds">The maximum milliseconds we should allow the max tasks to run in parallel before allowing another task to start. Specify -1 to wait indefinitely.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    public static void StartAndWaitAllThrottled(IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, int timeoutInMilliseconds, CancellationToken cancellationToken = new CancellationToken())
    {
        // Convert to a list of tasks so that we don&#39;t enumerate over it multiple times needlessly.
        var tasks = tasksToRun.ToList();

        using (var throttler = new SemaphoreSlim(maxTasksToRunInParallel))
        {
            var postTaskTasks = new List<Task>();

            // Have each task notify the throttler when it completes so that it decrements the number of tasks currently running.
            tasks.ForEach(t => postTaskTasks.Add(t.ContinueWith(tsk => throttler.Release())));

            // Start running each task.
            foreach (var task in tasks)
            {
                // Increment the number of tasks currently running and wait if too many are running.
                throttler.Wait(timeoutInMilliseconds, cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();
                task.Start();
            }

            // Wait for all of the provided tasks to complete.
            // We wait on the list of "post" tasks instead of the original tasks, otherwise there is a potential race condition where the throttler&#39;s using block is exited before some Tasks have had their "post" action completed, which references the throttler, resulting in an exception due to accessing a disposed object.
            Task.WaitAll(postTaskTasks.ToArray(), cancellationToken);
        }
    }

а затем создание списка задач и вызов функции для их запуска, скажем, максимум 3 одновременно, вы можете сделать следующее:

var listOfTasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
    var count = i;
    // Note that we create the Task here, but do not start it.
    listOfTasks.Add(new Task(() => Something()));
}
Tasks.StartAndWaitAllThrottled(listOfTasks, 3);