How to update UI from child tasks in WinForms

11,867

Solution 1

Yes, you can explicitly call BeginInvoke on the Window/Control that you want to communicate with. In your case this would look like this:

this.textBox.BeginInvoke(new Action(() =>
{
   this.textBox.Text = "From child task.";
}));

Solution 2

You're passing the TaskScheduler as state (or antecedent, as you called it). That doesn't make much sense.

I'm not sure what exactly do you want to do, but you need to specify the TaskScheduler when you're starting the Task, not when you're creating it. Also, it seems that childTask doesn't have to be a field:

var scheduler = TaskScheduler.FromCurrentSynchronizationContext();

Task childTask = null;

Task.Factory.StartNew(
    () =>
    {
        Thread.Sleep(1000);
        childTask.Start(scheduler);
        Thread.Sleep(1000);
    });

childTask = new Task(
    () =>
    {
        Thread.Sleep(2000);
        textBox1.Text = "From child task";
    });

Of course, all this is going to be much easier with C# 5 and await:

public Form1()
{
    InitializeComponent();

    StartAsync();
}

private async void StartAsync()
{
    // do some work
    await Task.Run(() => { Thread.Sleep(1000); });

    // start more work
    var moreWork = Task.Run(() => { Thread.Sleep(1000); });

    // update the UI, based on data from “some work”
    textBox1.Text = "From async method";

    // wait until “more work” finishes
    await moreWork;
}

You can't make async constructor, but you can run an async method from it. “Doing work” doesn't run on the UI thread, because it was started explicitly through Task.Run(). But since StartAsync() was called directly, it executes on the UI thread.

Share:
11,867
devlife
Author by

devlife

Developer, entrepreneur, child, adult, learning addict

Updated on June 05, 2022

Comments

  • devlife
    devlife over 1 year

    I've got a simple little winforms app that performs a long running process on another thread via a TPL Task. During this long running process I'd like to update the UI (the progress bar or something). Is there a way to do this without being required to .ContinueWith()?

    public partial class Form1 : Form
    {
        private Task _childTask;
    
        public Form1()
        {
            InitializeComponent();
    
            Task.Factory.StartNew(() =>
            {
                // Do some work
                Thread.Sleep(1000);
    
                // Update the UI
                _childTask.Start();
    
                // Do more work
                Thread.Sleep(1000);
            });
    
            _childTask = new Task((antecedent) =>
            {
                Thread.Sleep(2000);
                textBox1.Text = "From child task";
            }, TaskScheduler.FromCurrentSynchronizationContext());
    
    
        }
    }
    

    Executing this code I get the ubiquitous exception:

    Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.

    • jwillmer
      jwillmer over 11 years
      I think you have to paste the textbox reference to the thread.
  • devlife
    devlife over 11 years
    That definitely looks to be on the way but I get a compilation error: Cannot convert lambda expression to type 'System.Delegate' because it is not a delegate type
  • devlife
    devlife over 11 years
    That's just the point. I don't want to wait until one task is completed to execute the other.
  • devlife
    devlife over 11 years
    I created an Action and passed in the action instead of the anonymous method. Not sure why the above doesn't work but it got me 99% of the way there. Thanks Drew.
  • devlife
    devlife over 11 years
    I updated my example to better show what I was trying to get across.
  • svick
    svick over 11 years
    In that case, my first sample will still work for you. I also updated the C# 5 version.
  • Drew Marsh
    Drew Marsh over 11 years
    @devlife Oops, that's my fault for being so used to just working with newer APIs. Yes, you need to explicitly wrap with a new Action(...) because of the way the signature of the old WinForms BeginInvoke API was designed. I will update my answer.
  • devlife
    devlife over 11 years
    That's exactly what I ended up doing. Thanks.
  • devlife
    devlife over 11 years
    That is pretty much what I was trying to do (just didn't get it quite right). Can't wait to start using C#5....