5

Update - changed the title of the question to reflect what I'm really after

Consider the following piece of code:

// this query generates 12 instances of Func<int>, which each when executed
// print something to the console and wait for 1 second.
var actions = Enumerable.Range(0, 12).Select(i => new Func<int>(() =>
{
    Console.WriteLine("{0} - waiting 1 sec", i);
    Thread.Sleep(1000);
    return 1;
}));

// define a parallel query. Note the WithDegreeOfParallelism call here.
var query = from action in actions.AsParallel().WithDegreeOfParallelism(12)
            select action();

// execute, measuring total duration
var stopw = Stopwatch.StartNew();
query.ToList();
Console.WriteLine(stopw.Elapsed);
Console.WriteLine(Environment.ProcessorCount); // 3 on my machine

When omitting the call to WithDegreeOfParallelism, this executes in 4 chunks, taking about 4 seconds in total, which is what I would expect since my CPU count is 3.

However, when calling WithDegreeOfParallelism with any number above 4, I always get 3 chunks, and the total duration does not go under 3 seconds. I would expect that a value of 12 would get a total duration of (a little more than) 1 second.

What am I missing? And how can I enforce the parallel execution of more than 4 non-CPU intensive tasks, which is what I'm after?

Update: I could of course go back to manually spinning up threads, but I was hoping that the new PFX library would make this a bit easier... Anyway, the code below gives me about 1 second total execution time

List<Thread> threads = new List<Thread>();
for (int i = 0; i < 12; i++)
{
    int i1 = i;
    threads.Add(new Thread(() =>
    {
        Console.WriteLine(i1);
        Thread.Sleep(1000);
    }));
}
threads.ForEach(t => t.Start());
threads.ForEach(t => t.Join());
jeroenh
  • 26,362
  • 10
  • 73
  • 104
  • 2
    WithDegreeOfParallelism is setting only a upper bound. PLinq will choose best parallelism below that number – Jayantha Lal Sirisena Jul 27 '11 at 11:56
  • OK but how can it know that 4 is an optimal value for this case? Is there a way to enforce running this on 12 concurrent threads with PLINQ? – jeroenh Jul 27 '11 at 11:59
  • Actually the optimum for you is 3. Why would you want to run more threads than you have cores? Only the overhead increases. – H H Jul 27 '11 at 12:06
  • @Henk As indicated the tasks are non-cpu intensive. To me it really does make sense to run them on more than 3 threads in parallel, no? – jeroenh Jul 27 '11 at 12:10
  • I missed that "non-CPU" bit. But then don't experiment with CPU-bound code (or Sleep). Run it with some I/O to give the scheduler a chance to prove itself. – H H Jul 27 '11 at 12:17
  • What kind of non-cpu-intensive operations are you actually performing here? – Lasse V. Karlsen Jul 27 '11 at 12:20
  • @Lasse: calling a web service – jeroenh Jul 27 '11 at 12:26
  • 1
    @Henk Could you elaborate on why this should not be simulated with Thread.Sleep? – jeroenh Jul 27 '11 at 12:28
  • 2
    You should be using asynchronous code here, not tying up (threadpool-)threads like this. P/LINQ isn't the be-all do-all framework, it is good for cpu-intensive operations, but in this case you should use things like BeginXYZ operations on the web requests. This way you won't tie up threads. – Lasse V. Karlsen Jul 27 '11 at 12:30

3 Answers3

3

Try starting new tasks in your parallel loop with the option TaskCreationOptions.LongRunning. They will start right away, instead of waiting until a thread on the threadpool becomes available.

Gebb
  • 6,371
  • 3
  • 44
  • 56
2

As I said WithDegreeOfParallelism is setting only a upper bound.Try increasing your tasks from 10 to 100. You will ended up with around 10 seonds for all 100 of them. Your code is good for larger number of tasks having smaller operations. and add Console.WriteLine("{0} threads " ,Process.GetCurrentProcess().Threads.Count); inside your task then you can see how many threads are created.( Thread count is not the count of plinq created threads. See how it increasing).

There are lots of ways to do the parallelism with PLinq . Read this article http://msdn.microsoft.com/en-us/library/dd997411.aspx. You need to choose best way for the relevant requirement to get better performance.

Jayantha Lal Sirisena
  • 21,216
  • 11
  • 71
  • 92
2

WithDegreeOfParallelism dictates how many Tasks should PLINQ create, but not necessarily how many threads will be used.

Since Tasks execute as work items on the ThreadPool, the number of threads executing the query will be limited the size of the ThreadPool. The ThreadPool will add threads as necessary, but it may take a while - ThreadPool may add 2 threads per second or so.

If you want to add threads to the ThreadPool quickly, you can use the SetMinThreads method. If you put this code at the beginning of your code, the test should complete in a second or so:

int prevThreads, prevPorts;
ThreadPool.GetMinThreads(out prevThreads, out prevPorts);
ThreadPool.SetMinThreads(12, prevPorts);

You can decide how many threads you need, and then use SetMinThreads and SetMaxThreads to set bounds on the ThreadPool size.

Igor ostrovsky
  • 7,282
  • 2
  • 29
  • 28