4

I know that there is no need to wait for a Parallel.For but since UI Windows messages (WinForms message pump) are being processed during the Parallel.For I would like to wait for Parallel.For to finish.

Is it a good idea to encapsulate a Parallel.For inside a Task, then wait for it? Is there any better approach?

Thanks.

CancellationTokenSource token = new CancellationTokenSource();

const int len = 100;

double[] array = new double[len];

Task t = Task.Factory.StartNew(delegate {
   Parallel.For(0, len, delegate(int i, ParallelLoopState loopState) {

   array[i] += 1;

   });

try
{
   t.Wait(token.Token);
}
catch (OperationCanceledException e)
{
   rc = false;
}
abenci
  • 8,422
  • 19
  • 69
  • 134
  • 3
    `Parallel.For` already blocks your current thread till all tasks are done. – L.B Feb 10 '14 at 15:30
  • `Parallel.For` doesn't return until its done. If you wrap it in a `Task`, then that task will complete at "exactly" the same point in time at which the `Parallel.For` returns at. You've not added anything. – Damien_The_Unbeliever Feb 10 '14 at 15:31
  • This is not true, WM_PAINT messages are arriving an being processed... – abenci Feb 10 '14 at 15:32
  • 1
    @Alberto That may be so, but it doesn't return until it has completed. Adding a couple of Debug.WriteLine() calls around it and running under the debugger will show you. – Matthew Watson Feb 10 '14 at 15:34
  • 2
    But what makes you think that `Task.Wait` is going to wait for `WM_PAINT` messages any more than `Parallel.For` does? – Damien_The_Unbeliever Feb 10 '14 at 15:35
  • Because it works. `Task.Wait` is called from the UI thread... – abenci Feb 10 '14 at 15:38
  • 4
    Well, your experience seems to be completely at odds with most peoples. Can you construct a short but *complete* sample that demonstrates both with and without the use of the extra `Task` and that clearly shows the difference between them? – Damien_The_Unbeliever Feb 10 '14 at 15:46
  • @Damien: I changed the sample, so it's more clear to you all. The benefit in using the `Parallel.For' is that it will take the 25% of time on a quad core CPU. – abenci Feb 10 '14 at 16:03

3 Answers3

4

Instead of Parallel.For why not use just Task and call Task.WaitAll()?

var t1 = Task.Run(() => {});
var t2 = Task.Run(() => {});

Task.WaitAll(t1, t2);
Mayank
  • 8,777
  • 4
  • 35
  • 60
4

If you want to wait for a Parallel.For() to finish, you simply don't start it in a separate task!

Parallel.For() doesn't return until it has completed (even if it uses multiple threads to do the work).

Note that Parallel.For() returns a ParallelLoopResult - it does not return a task or anything else which would allow you to wait for it to finish, so if it did return before it was complete (as you are asserting) there would be no way to know when it had finished.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • @Alberto Yes it is true. Just add some debug messages around the calls. What you are seeing is the message loop being pumped while the Parallel.For() is running - but the Parallel.For() does NOT return until it has completed. – Matthew Watson Feb 10 '14 at 15:40
  • Please try before commenting... We tested it twice before adding this question. – abenci Feb 10 '14 at 15:44
  • @Alberto It definitely doesn't return until it's finished. If it did, chaos would ensue because you would end up using the results of the Parallel.For() before it had finished! The paint messages are not a sign that Parallel.For() has returned before it has finished. – Matthew Watson Feb 10 '14 at 15:49
  • 1
    @Alberto: Based on your comments, I think you need to explain what behaviour you're actually seeing. You say you "tested it twice" but nowhere do you explain what your test was, or what behaviour you saw that you're trying to avoid. Saying "`WM_` messages aren't working" doesn't give much to go on. – Dan Puzey Feb 10 '14 at 15:52
  • @Dan: It's specified in the question that UI `WM_` messages are being processes while the `Parallel.For` loop is working. – abenci Feb 10 '14 at 16:21
  • "Parallel.For() doesn't return until it has completed (even if it uses multiple threads to do the work)." - The ParallelLoopResult.IsCompleted property can be misleading! – Żubrówka Mar 11 '20 at 09:20
  • @Żubrówka How so? [The documentation](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallelloopresult.iscompleted?view=netframework-4.8) is quite clear, I think. (When I said "Completed" I meant "has no further work to do" rather than "finished and was not cancelled".) – Matthew Watson Mar 11 '20 at 09:27
  • @MatthewWatson Parallel.For(....). :) – Żubrówka Mar 11 '20 at 09:28
  • @Żubrówka Yes, it would have been better called "CompletedWithoutCancellation" or something like that, I guess. – Matthew Watson Mar 11 '20 at 09:29
3

What makes you be so confident that WM_PAINT and other Windows messages are pumped while Parallel.For or Task.Wait is blocking on the UI thread?

The following simple example proves you're wrong. The form is not getting repainted to red for the whole 15 seconds, while Parallel.For is working.

using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinFroms_21681229
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();

            this.Shown += MainForm_Shown;
        }

        void MainForm_Shown(object sender, EventArgs e)
        {
            MessageBox.Show("Before");

            this.BackColor = Color.FromName("red");
            this.Invalidate();

            // the form is not getting red for another 15 seconds
            var array = new double[] { 1, 2, 3 };
            Parallel.For(0, array.Length, (i) =>
            {
                System.Threading.Thread.Sleep(5000);
                Debug.Print("data: " + array[i]);
            });

            MessageBox.Show("After");
        }
    }
}

Here is how to run the tasks in parallel, while keeping the UI responsive:

async void MainForm_Shown(object sender, EventArgs e)
{
    MessageBox.Show("Before");

    this.BackColor = Color.FromName("red");
    this.Invalidate();

    // the form is not getting red for another 15 seconds
    var array = new double[] { 1, 2, 4 };

    var tasks = array.Select((n) => Task.Run(()=> 
    {
        Thread.Sleep(5000);
        Debug.Print("data: " + n);
    }));

    await Task.WhenAll(tasks);

    MessageBox.Show("After");
}

You could have done something like await Task.Factory.StartNew(() => Parallel.For(...)), but that would use at least one more thread than really needed.

To understand what's going on behind the scene here, you'd need to understand how WinForms message loop works inside Application.Run, how await yields the execution control back to the message loop and then gets continued via WindowsFormsSynchronizationContext, when the task has completed. The async-await tag wiki could help, it contains links to some great, must-read resources.

If you're interested to know how message pumping works during blocking operations, check this answer.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    Small suggestion, if you are going to be running a task that takes more than a few seconds to complete you should not use `Task.Run(` as that will tie up a thread pool thread, instead use [`Task.Factory.StartNew(`](http://msdn.microsoft.com/en-us/library/dd321263%28v=vs.110%29.aspx) and pass in `TaskCreationOptions.LongRunning`. This will (if using the default task scheduler) cause your task to be run on a full `Thread` with `IsBackground` set instead of on a ThreadPool thread. – Scott Chamberlain Feb 11 '14 at 02:11