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'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'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.