4

I started off trying to add a progress bar to the windows form that updates the progress of code running within a Parallel.Foreach loop. In order to do this the UI thread has to be available to update the progress bar. I used a Task to run the Parallel.Foreach loop to allow the UI thread to update the progress bar.

The work done within the Parallel.Foreach loop is rather intensive. After running the executables of the program(not debugging within visual studio) with the Task, the program became unresponsive. This is not the case if I run my program without Task. The key difference I noticed between the two instances is that the program takes ~80% of the cpu when ran without Task, and ~5% when ran with Task.

private void btnGenerate_Click(object sender, EventArgs e)
    {
        var list = GenerateList();
        int value = 0;
        var progressLock = new object ();

        progressBar1.Maximum = list.Count();

        Task t = new Task(() => Parallel.ForEach (list, item =>
        {
                DoWork ();
                lock (progressLock)
                {
                    value += 1;
                }
        }));

        t.Start();

        while (!t.IsCompleted)
        {
            progressBar1.Value = value;
            Thread.Sleep (100);
        }
    }

Side Note: I know that

 Interlocked.Increment(ref int___);

works in place of the lock. Is it considered more efficient?

My Question is three fold:

1.) Why would the program with the Task become unresponsive when the load is much less?

2.) Does using Task to run Parallel.Foreach limit the thread pool of the Parallel.Foreach to only the thread running the task?

3.) Is there a way to make the UI thread responsive instead of sleeping for the .1 second duration without using cancellation token?

I'm grateful for any help or ideas, I've spent quite a lot of time researching this. I also apologize if I've violated any posting format or rules. I tried to adhere to them, but may have missed something.

EN Speer
  • 45
  • 1
  • 6
  • Waiting for the task to complete in a loop on the UI thread is just like executing it on the UI thread (slightly worse, even). Look into `BackgroundWorker` or `async` – SimpleVar May 29 '15 at 01:03
  • If `DoWork()` access any of your UI elements, it will cause a deadlock to occur. – Johnathon Sullinger May 29 '15 at 01:22
  • JohnathonSullinger - DoWork() does not access any UI elements. @Yorye Nathan - Could you elaborate or give an example? I have never done anything with multi-threading before today. – EN Speer May 29 '15 at 01:37
  • Maybe something related to this? http://stackoverflow.com/questions/17248680/await-works-but-calling-task-result-hangs-deadlocks – dbc May 29 '15 at 01:46
  • possible duplicate of [Support of progress reporting and incremental results in .NET 4.0 "Task Parallel Library"](http://stackoverflow.com/questions/1535749/support-of-progress-reporting-and-incremental-results-in-net-4-0-task-parallel) – Johnathon Sullinger May 29 '15 at 01:56
  • You say you are using .NET 4.0 but `Task.Run` is a .NET 4.5 feature. – Scott Chamberlain May 29 '15 at 02:38
  • Correct. When I was typing the sample code I neglected to create the new task and start it. I'll make an edit. – EN Speer May 29 '15 at 19:02

1 Answers1

6

You can greatly simplify your code there by using the built in Invoke method that invokes a delegate on the owning Windows synchronization context.

From MSDN:

Executes the specified delegate on the thread that owns the control's underlying window handle.

The Invoke method searches up the control's parent chain until it finds a control or form that has a window handle if the current control's underlying window handle does not exist yet.

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

    string[] GenerateList() => new string[500];
    void DoWork()
    {
        Thread.Sleep(50);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var list = GenerateList();
        progressBar1.Maximum = list.Length;

        Task.Run(() => Parallel.ForEach(list, item =>
        {
            DoWork();

            // Update the progress bar on the Synchronization Context that owns this Form.
            this.Invoke(new Action(() => this.progressBar1.Value++));
        }));
    }
}

This will invoke the Action delegate on the same UI thread that the Form belongs to, from within the Task.

Now to try and answer your questions

1.) Why would the program with the Task become unresponsive when the load is much less?

I'm not 100% sure, but this could be related to you locking a member on the UI thread. If the load is less, then the lock will happen more frequently, potentially causing the UI thread to "hang" while the progressbar is incremented.

You are also running a while loop that is sleeping the UI thread every 100 milliseconds. You'll see UI hanging due to that while loop.

2.) Does using Task to run Parallel.Foreach limit the thread pool of the Parallel.Foreach to only the thread running the task?

It does not. Several tasks will get created within the Parallel.ForEach call. The underlying ForEach uses a partitioner to spread the work out, and not create more tasks than what is necessary. It creates tasks in batches, and processes the batches.

3.) Is there a way to make the UI thread responsive instead of sleeping for the .1 second duration without using cancellation token?

I was able to handle that by removing the while loop and using the Invoke method to just go ahead and execute a lambda on the UI thread directly.

Johnathon Sullinger
  • 7,097
  • 5
  • 37
  • 102
  • That's a great solution for .Net 4.5, but the question is tagged .net-4.0. Could OP use a [`BeginInvoke()`](https://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke%28v=vs.110%29.aspx) to update the progressbar? – dbc May 29 '15 at 01:51
  • Ah, I missed that. Yes they can. I'll update my answer with an example. – Johnathon Sullinger May 29 '15 at 01:55
  • Thanks for the response! Unfortunately the project I'm working on is using .NET 4.0 which, as far as I know, doesn't support the Progress(T) Class. edit: Didn't see that someone already caught that. – EN Speer May 29 '15 at 01:56
  • 1
    @ENSpeer Microsoft provides `Progres` in 4.0, you just need to install the NuGet package [`Microsoft.Bcl`](https://www.nuget.org/packages/Microsoft.Bcl/) – Scott Chamberlain May 29 '15 at 02:37
  • Also sorry to do it but I have to do a -1 for the advice *"It's always good to use `DoEvents()` in your while loops on the UI thread."*, its almost never good to do `DoEvents()` in a loop, if you find you need to use `DoEvents()` in a loop the correct advice is "you should rewrite your code to not need to do a while loop on the UI thread". You cover the correct way in your answer (use `Invoke`) but you should not have recommended `DoEvents()` – Scott Chamberlain May 29 '15 at 02:45
  • Can you educate me on why it's bad? A link on it would be great if its to long for a single comment, I wasn't aware of that unfortunately. I mentioned that if you _have_ to use a while loop, then calling DoEvents allows the UI to paint itself. My recommendation was not to be using a `while` loop, hence why my answer is `Invoke`. – Johnathon Sullinger May 29 '15 at 02:46
  • http://stackoverflow.com/questions/5181777/use-of-application-doevents Hans Passant's answer (I consider him to be one of the best experts on this site for C# UI topics) covers it well – Scott Chamberlain May 29 '15 at 02:46
  • Nice post, thanks for the link. I think in this scenario, he should continue to use `Invoke` instead of `IProgress` – Johnathon Sullinger May 29 '15 at 02:48
  • I removed the reference to `Application.DoEvents()` so as to not misinform others in the future who read the answer. – Johnathon Sullinger May 29 '15 at 03:28
  • And I turned the -1 to a +1 because other than that one piece of bad advice it was a pretty good answer. – Scott Chamberlain May 29 '15 at 05:07