System.Threading.Tasks - ограничение количества одновременных задач

Я только начал изучать доброту "System.Threading.Tasks" в .Net 4.0 и хотел бы знать, есть ли какая-либо поддержка в поддержке для ограничения количества одновременных задач, которые запускаются сразу, или если это нужно обрабатывать вручную.

E.G: Если мне нужно вызвать метод расчета 100 раз, существует ли способ настроить 100 задач, но только 5 выполняются одновременно? Ответ может состоять только в том, чтобы создать 5 задач, вызвать Task.WaitAny и создать новую задачу по мере завершения каждой предыдущей. Я просто хочу убедиться, что я не упускаю трюк, если есть лучший способ сделать это.

В принципе, есть встроенный способ сделать это:

Dim taskArray() = {New Task(Function() DoComputation1()),
                   New Task(Function() DoComputation2()),
                   ...
                   New Task(Function() DoComputation100())}

Dim maxConcurrentThreads As Integer = 5
RunAllTasks(taskArray, maxConcurrentThreads)

Спасибо за любую помощь.

Ответ 1

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

Dim actionsArray() As Action = 
     new Action(){
         New Action(Sub() DoComputation1()),
         New Action(Sub() DoComputation2()),
         ...
         New Action(Sub() DoComputation100())
      }

System.Threading.Tasks.Parallel.Invoke(New Tasks.ParallelOptions() With {.MaxDegreeOfParallelism = 5}, actionsArray)

Voila!

Ответ 2

Я знаю, что это старый поток, но я просто хотел поделиться своим решением этой проблемы: использовать семафоры.

(Это в С#)

private void RunAllActions(IEnumerable<Action> actions, int maxConcurrency)
{
    using(SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxConcurrency))
    {
        foreach(Action action in actions)
        {
            Task.Factory.StartNew(() =>
            {
                concurrencySemaphore.Wait();
                try
                {
                    action();
                }
                finally
                {
                    concurrencySemaphore.Release();
                }
            });
        }
    }
}

Ответ 3

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

Описание выглядит следующим образом: "Предоставляет планировщик задач, который обеспечивает максимальный уровень concurrency во время работы поверх ThreadPool"., и, насколько я смог проверить, похоже, это трюк, таким же образом, как свойство MaxDegreeOfParallelism в ParallelOptions.

Ответ 4

С# эквивалент образца, предоставленный Джеймсом

Action[] actionsArray = new Action[] {
new Action(() => DoComputation1()),
new Action(() => DoComputation2()),
    //...
new Action(() => DoComputation100())
  };

   System.Threading.Tasks.Parallel.Invoke(new Tasks.ParallelOptions {MaxDegreeOfParallelism =  5 }, actionsArray)

Ответ 5

Короткий ответ: Если вы хотите ограничить количество рабочих задач, чтобы они не насыщали ваш веб-сервис, я думаю, что ваш подход прекрасен.

Длинный ответ: Новый движок System.Threading.Tasks в .NET 4.0 работает поверх .NET ThreadPool. Так как существует только один ThreadPool для каждого процесса и по умолчанию не более 250 рабочих потоков. Поэтому, если вы должны установить максимальное число потоков ThreadPool на более скромное число, вы можете уменьшить количество одновременно выполняемых потоков и, следовательно, задачи с использованием API ThreadPool.SetMaxThreads (...).

ОДНАКО, обратите внимание, что вы, возможно, не одиноки в использовании ThreadPool, так как многие другие классы, которые вы используете, также могут помещать элементы в ThreadPool. Таким образом, есть хороший шанс, что вы можете в конечном итоге испортить остальную часть вашего приложения, сделав это. Также обратите внимание, что поскольку ThreadPool использует алгоритм для оптимизации использования данного ядра, лежащего в основе ядра, ограничение количества потоков, которые threadpool может поставить в очередь на произвольно низкое число, может привести к некоторым довольно катастрофическим проблемам производительности.

Опять же, если вы хотите выполнить небольшое количество рабочих задач/потоков для выполнения какой-либо задачи, лучшим решением будет только создание небольшого количества задач (против 100).

Ответ 6

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

С помощью действий

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

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

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

С задачами

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

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

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

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

Ответ 7

Это не похоже на это, хотя вы можете создать подкласс TaskScheduler, который реализует такое поведение.

Ответ 8

Если ваша программа использует веб-службы, количество одновременных подключений будет ограничено свойством ServicePointManager.DefaultConnectionLimit. Если вам требуется 5 одновременных подключений, недостаточно использовать решение Arrow_Raider. Вы также должны увеличить ServicePointManager.DefaultConnectionLimit, потому что по умолчанию оно равно 2.