0

The code below is a simplified example of a problem I am having. What happens upon the form loading - the For Loop will create a new task per iteration, then go as far as entering the 'if (pic.InvokeRequired)' section, but will return back to the For Loop and continue to iterate BEFORE any of the Tasks go through their respective invocations of method() after pic.BeginInvoke() is called.

What I am trying to achieve is for the invoking to complete it's second pass through method() and eventually changing the pic.BackColor to Color.Blue. I imagine this is possible, no? I've spent a couple hours searching around and could not find a satisfactory answer to this problem..

To run this code yourself, make a new WinForms Application Project (I'm using Visual Studio 2012) and add a PictureBox called 'pic'. Insert the code below.

Thank you for any and all help!

private void Form1_Load(object sender, EventArgs e)
{
    for (int i = 0; i < 5; i++)
    {
        Task task = Task.Factory.StartNew(() => method());
        task.Wait(); //Waits for task to complete before proceeding
    }
}

public delegate void delegate_method();
private void method()
{
    if (pic.InvokeRequired)
    {
        delegate_method dm = new delegate_method(() => method());
        pic.BeginInvoke(dm); //If ran once (without the loop in Form1_Load) it would call 'method()' immediately from here.
    }
    else
    {
        pic.BackColor = Color.Blue;
    }
}
  • Use the Form.Activated event instead. – thepirat000 Nov 26 '13 at 04:30
  • Still conflicted by cross-threading. If incorporating the Form.Activate event worked for you, could you post a modification of my example please? – user3034748 Nov 28 '13 at 05:32
  • The way I'm reading the question what you want to do is block while you're waiting for an async invoke to complete. The only way you'd be able to do that (at least the only way I can see in this situation), is by replacing your `task.Wait();` with `while (!task.IsCompleted) Application.DoEvents();` and `pic.BeginInvoke(dm);` with `pic.Invoke(dm);`, which I absolutely do not recommend due to the evil nature of `Application.DoEvents()`. Instead you should look at making your code which makes the initial call to `method()` properly async - avoiding blocking (i.e. `task.Wait()`) altogether. – Kirill Shlenskiy Nov 28 '13 at 06:01
  • Why even create a new task, if you are going to wait for it to be done? You gain nothing. – Shawn Kendrot Nov 28 '13 at 06:13

1 Answers1

0

I can appreciate the question because in some situations blocking may be preferable to full-on async (although it's more of a last resort measure).

The answer here depends on whether your async call (the actual meat of the work which happens in the background before you get to invoke an action on the UI thread - the part of the code which I'm assuming you chose to omit for the sake of simplicity) does any UI-bound work. If it does, you're out of luck and using Application.DoEvents() is your best bet.

If, however, it does not, and you have enough control over the async code, what you can do is instead of trying to invoke the UI from within your task, pass the Action describing the work back to your UI thread (as your Task's Result) and then handle its invocation there.

Note that I've simplified the implementation of Method() as it no longer gets called from non-UI threads.

WARNING: SIMPLIFIED CODE, NOT SUITABLE FOR PRODUCTION.

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

namespace PicInvoke
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            this.InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Stopwatch sw;

            // Sequential processing.
            sw = Stopwatch.StartNew();

            this.DoWorkAndBlockSequential();

            sw.Stop();

            MessageBox.Show(string.Format("Sequential work complete. Time taken: {0:0.000}s.", (double)sw.ElapsedMilliseconds / 1000.0));

            // Parallel processing.
            sw = Stopwatch.StartNew();

            this.DoWorkAndBlockParallel();

            sw.Stop();

            MessageBox.Show(string.Format("Parallel work complete. Time taken: {0:0.000}s.", (double)sw.ElapsedMilliseconds / 1000.0));
        }

        private void DoWorkAndBlockSequential()
        {
            for (int i = 0; i < 5; i++)
            {
                var task = this.DoWorkAsync();

                // Block the UI thread until the task has completed.
                var action = task.Result;

                // Invoke the delegate.
                action();
            }
        }

        private void DoWorkAndBlockParallel()
        {
            var tasks = Enumerable
                .Range(0, 5)
                .Select(_ => this.DoWorkAsync())
                .ToArray();

            // Block UI thread until all tasks complete.
            Task.WaitAll(tasks);

            foreach (var task in tasks)
            {
                var action = task.Result;

                // Invoke the delegate.
                action();
            }
        }

        private Task<Action> DoWorkAsync()
        {
            // Note: this CANNOT synchronously post messages
            // to the UI thread as that will cause a deadlock.
            return Task
                // Simulate async work.
                .Delay(TimeSpan.FromSeconds(1))
                // Tell the UI thread what needs to be done via Task.Result.
                // We are not performing the work here - merely telling the
                // caller what needs to be done.
                .ContinueWith(
                    _ => new Action(this.Method),
                    TaskContinuationOptions.ExecuteSynchronously);
        }

        private void Method()
        {
            pic.BackColor = Color.Blue;
        }
    }
}
Kirill Shlenskiy
  • 9,367
  • 27
  • 39
  • Kirill, thank you very much. I gained a lot from the example you've posted and it's more than enough for me to remedy my problem. I appreciate your help. – user3034748 Dec 01 '13 at 18:17