2

I have a windows C# WinForms application. I'm confused by the way the default task scheduler is behaving for parallel class in task parallel library. When I call Parallel.For inside ParallelForEach method I was expecting the numbers to get printed in jumbled fashion in output window through Console.WriteLine statement inside "Method1" method.

Since I'm not passing any ParallelOptions to configure the scheduler for Parallel class it should be using default task scheduler but numbers are getting printed in serial fashion like 0,1,2,3,.....49. This gives me an impression that all the tasks which are getting executed are running on UI synchronization context in serial fashion one by one. I also verified this by updating the text property of UI text box with value of i and it didn't throw me an InvalidOperationException which happens in case of thread-pool thread.

But if I uncomment the first statement inside Method1 function to simulate long running task the output gets jumbled. Why is the CLR is making this decision to use the UI thread to run all those 50 tasks? Why is the CLR is making this decision on my behalf when I have not asked it to do so just on the basis of the fact that initially my task is small compute bound task? How does the CLR really evaluate the fact that a particular method will take long time to execute or small time to execute?

Hardware information: My computer is a Windows Server 2008 box with four cores.

namespace WindowsFormsApplication1
{
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Schedulers;
using System.Windows.Forms;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        ParallelForEach();
    }

    private void ParallelForEach()
    {
        Console.WriteLine("With default task scheduler");
        Parallel.For(0, 50, i => Method1(i));
     }

    private void Method1(int i)
    {
        //System.Threading.Thread.Sleep(2000);
        //textBox1.Text = i.ToString();
        Console.WriteLine(i);
        //do some work   
    }
}
}
RBT
  • 24,161
  • 21
  • 159
  • 240
  • Related - http://stackoverflow.com/questions/18943703/how-many-threads-parallel-forforeach-will-create-default-maxdegreeofparalleli. You may try to dig into the [source code](http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Parallel.cs,ceb354fb1788c81a), but I actually doubt that there exists some super clever algorithm (some heuristics dependent on internal and external factors, but nothing more), though it is possible that some part of that is hidden in external implementation methods, so you won't get to see them. – Eugene Podskal Feb 28 '16 at 15:53
  • In such a case .NET Core may be more useful - https://github.com/dotnet/corefx/blob/39471b56bd0241e60a33b4179a04187b96481547/src/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs. – Eugene Podskal Feb 28 '16 at 15:54
  • And I doubt that SynchronizationContext has anything to do with the behaviour you encounter. The most probable thing is that Paraller.For tries to do some part of the job on the original thread and starts using other threads only if takes more than a specific amount of time. – Eugene Podskal Feb 28 '16 at 15:57

1 Answers1

3

By default, Parallel.For() uses the default TaskScheduler (which uses the ThreadPool) and also the current thread (it has to be blocked until all iterations are complete, so it might as well do some useful work, instead of just waiting). So, if you have a small number iterations that finish fast, it's possible that the whole loop will execute on the current loop, before all the Tasks that are scheduled to the TaskScheduler even start. This explains the behavior you're seeing in both cases.


Note that you shouldn't block the UI thread for a long period of time, doing that freezes the UI of the application. This implies that you shouldn't use Parallel.For() on the UI thread.

svick
  • 236,525
  • 50
  • 385
  • 514
  • What you mean is - the thread finish the iteration so fast, it quickly picks up the next Task? And in the other example, another thread picks up the next Task while the first one is blocked on `Sleep()`? – shay__ Feb 28 '16 at 20:54
  • @shay__ Kind of, except that `Parallel.For(0, 50, …)` does not mean there are 50 `Task`s. So, the main thread completes executing all the iterations before the `Task`s are even assigned a thread. – svick Feb 28 '16 at 21:26
  • @svick I was also under similar impression but I got confused only when I read about ParallelOptions class which can be passed as parameter to Parallel.For method. ParallelOptions class has a property "TaskScheduler" to choose the designated TaskScheduler of your choice. Its default value is "TaskScheduler.Default". Now I got your point that this philosophy of usage of main thread is applicable to all methods of Parallel class irrespective of the choice we make to configure the scheduler associated with it. I did one more thing I increased loop counter to 500 and then the output got jumbled :) – RBT Feb 29 '16 at 00:36
  • @shay__ just to add what svick has mentioned. As a default behavior Task scheduler generally schedules as many tasks as there are CPU cores on your machine (4 in my case). This is configurable through ParallelOptions class which can be passed as parameter to Parallel.For method. – RBT Feb 29 '16 at 00:38
  • 1
    Actually this is a great example of a case where Task based parallelism is not only easier than Thread based parallelism, but also more efficient! (the first example, without `Sleep()`) – shay__ Feb 29 '16 at 07:07