Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update UI from child tasks in WinForms

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.

like image 741
devlife Avatar asked Mar 12 '12 19:03

devlife


People also ask

Can we update UI from thread?

Worker threads However, note that you cannot update the UI from any thread other than the UI thread or the "main" thread. To fix this problem, Android offers several ways to access the UI thread from other threads. Here is a list of methods that can help: Activity.

What are different ways of updating UI from background thread?

In this case, to update the UI from a background thread, you can create a handler attached to the UI thread, and then post an action as a Runnable : Handler handler = new Handler(Looper. getMainLooper()); handler. post(new Runnable() { @Override public void run() { // update the ui from here } });

How do I refresh WinForm?

You can use the Form. Invalidate(); or Form. Refresh(); methods.

Are WinForms obsolete?

Win Form has been used to develop many applications. Because of its high age (born in 2003), WinForm was officially declared dead by Microsoft in 2014. However, Win Form is still alive and well.


2 Answers

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.";
}));
like image 70
Drew Marsh Avatar answered Oct 03 '22 18:10

Drew Marsh


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.

like image 33
svick Avatar answered Oct 03 '22 17:10

svick



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!