Using TPL how do I set a max threadpool size

2019-01-24 07:57发布

I am using the TPL to add new tasks to the system thread pool using the function Task.Factory.StartNew(). The only problem is that I am adding a lot of threads and I think it is creating too many for my processor to handle. Is there a way to set a maximum number of threads in this thread pool?

4条回答
一夜七次
2楼-- · 2019-01-24 08:06

Typically TPL determines a good "default" threadpool size. If you really need fewer threads, see How to: Create a Task Scheduler That Limits the Degree of Concurrency

查看更多
姐就是有狂的资本
3楼-- · 2019-01-24 08:12

The default TaskScheduler (obtained from TaskScheduler.Default) is of type (internal class) ThreadPoolTaskScheduler. This implementation uses the ThreadPool class to queue tasks (if the Task isn't created with TaskCreationOptions.LongRunning - in this case a new thread is created for each task).

So, if you want to limit the # of threads available to Task objects created via new Task(() => Console.WriteLine("In task")), you can limit the available threads in the global threadpool like this:

// Limit threadpool size
int workerThreads, completionPortThreads;
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
workerThreads = 32;
ThreadPool.SetMaxThreads(workerThreads, completionPortThreads);

The call to ThreadPool.GetMaxThreads() is done to avoid shrinking the completionPortThreads.

Note that this may be a bad idea - since all Tasks without a specified scheduler, and any number of other classes use the default ThreadPool, setting the size too low could cause side-effects: Starvation, etc.

查看更多
4楼-- · 2019-01-24 08:15

Usually the TPL scheduler should do a good job of choosing how many tasks to run concurrently, but if you really want to have control over it My blog post shows how to do this both with Tasks and with Actions, and provides a sample project you can download and run to see both in action.

An example of when you might want to explicitly limit how many tasks are ran concurrently is when you are calling your own services and do not want to overload your server.

For what you are describing, it sounds like you may benefit more from making sure you are using async/await with your tasks to prevent needless thread consumption. This will depend though on if you are doing CPU-bound work, or IO-bound work in your tasks. If it's IO-bound, then you may benefit greatly from using async/await.

Regardless, you asked how you can limit the number of tasks that run concurrently, so here's some code to show how to do it with both Actions and Tasks.

With Actions

If using Actions, you can use the built-in .Net Parallel.Invoke function. Here we limit it to running at most 3 threads in parallel.

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());

With Tasks

Since you are using Tasks here though, there is no built-in function. However, you can use the one that I provide on my blog.

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

And then creating your list of Tasks and calling the function to have them run, with say a maximum of 3 simultaneous at a time, you could do this:

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);
查看更多
The star\"
5楼-- · 2019-01-24 08:33

You should investigate your performance problems first. There are various issues that may result in reduced utilization:

  • Scheduling long-running tasks without the LongRunningTask option
  • Trying to open more than two concurrent connections to the same web address
  • Blocking for access to the same resource
  • Trying to access the UI thread using Invoke() from multiple threads

In any case you have a scalability issue that can't be addressed simply by reducing the number of concurrent tasks. Your program may run in a two-,four-, or eight-core machine in the future. Limiting the number of scheduled tasks will simply lead to waste of CPU resources.

查看更多
登录 后发表回答