echolocation
echolocation

Reputation: 1130

How to update UI (text fields) during long running function?

I know that the question may not make sense, and I'm having a tough time trying to think of a way to explain it, so I will show a snippet of code to help. I'm using Winforms on visual studio express 2010:

private void button1(object sender, EventArgs e)
    {
        txtOutput.Text += "Auto-collecting variables. This may take several minutes";
        string v = foo();
        txtOutput.Text += "\n" + v;
        string b = bar();
        txtOutput.Text += "\n" + b;

        txtOutput.SelectionStart = txtOutput.Text.Length;
        txtOutput.ScrollToCaret(); //scrolls to the bottom of textbox
    }

So basically, when the user clicks button1, I want "Auto-collecting variables..." to be displayed in the textbox, and then have foo() execute, display that, and then have bar() execute, and then display that.

What is currently happening is that foo() and bar() execute, and then everything is displayed all at once after foo() and bar() have executed (functions that take several minutes). Is there anyway to fix this, or is there a work around?

Edit: Version of C# is 4.0. If I update to 4.5 or 5.0, will computers without .NET 4.5/5.0 be able to run the .exe?

Upvotes: 3

Views: 6876

Answers (5)

Servy
Servy

Reputation: 203837

C# 5.0 makes doing this trivial.

Execute the long running tasks in a background thread using Task.Run and use await to execute the rest of the method as a continuation in the UI thread without blocking the UI thread for the duration of the asynchronous task.

private async void button1(object sender, EventArgs e)
{
    txtOutput.Text += "Auto-collecting variables. This may take several minutes";
    string v = await Task.Run(() => foo());
    txtOutput.Text += "\n" + v;
    string b = await Task.Run(() => bar());
    txtOutput.Text += "\n" + b;

    txtOutput.SelectionStart = txtOutput.Text.Length;
    txtOutput.ScrollToCaret(); //scrolls to the bottom of textbox
}

You can do the same in C# 4.0 like so: (The first solution will be transformed by the compiler into something similar.)

private  void button1(object sender, EventArgs e)
{
    txtOutput.Text += "Auto-collecting variables. This may take several minutes";
    Task.Factory.StartNew(() => foo())
        .ContinueWith(t => txtOutput.Text += "\n" + t.Result
            , TaskScheduler.FromCurrentSynchronizationContext())
        .ContinueWith(t => bar())
        .ContinueWith(t =>
        {
            txtOutput.Text += "\n" + t.Result;
            txtOutput.SelectionStart = txtOutput.Text.Length;
            txtOutput.ScrollToCaret(); //scrolls to the bottom of textbox
        }
            , TaskScheduler.FromCurrentSynchronizationContext());
}

Upvotes: 6

Justin Pihony
Justin Pihony

Reputation: 67115

Depending on the version of .NET, you can use BackgroundWorker (Pre 4.0) or Tasks (Post 4.0 - 3.5 with an add-on)...to name a few.

Backgroundworker Pseudocode:

var backgroundWorker = new BackgroundWorker()

method
{
    //Update UI
    backgroundWorker.RunWorkAsync()
}

asyncworkmethod
{
    //do main logic
}

asynccompletemethod
{
    //Update UI to say done
}

Task Pseudocode:

method
{
     //Update UI
     TaskFactory.StartNew(()=>DoWork).ContinueWith((previousTask)=>UpdateUIToSayDone)
}

And, if you are using 4.5, then you can use the async/await keyword, however that is just syntactic sugar around tasks (mostly...). Servy already has a decent example of this, though if you go that approach

Upvotes: 2

tia
tia

Reputation: 9708

txtOutput.Update() should do what you want, but you should consider using background thread to complete long running task without blocking UI thread.

Upvotes: 0

Knaģis
Knaģis

Reputation: 21485

Using a background process (read the other answers) is the correct way to go but if you are looking at a very quick workaround you can call Application.DoEvents() after updating the TextBox. In most cases this call will result in your form updating to reflect the changes you made.

Upvotes: 1

Ben Voigt
Ben Voigt

Reputation: 283783

Use the BackgroundWorker class to do your processing without blocking UI updates. It has events that can be used to transfer progress information to the UI thread.

Upvotes: 2

Related Questions