3

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?

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
echolocation
  • 1,120
  • 1
  • 10
  • 28
  • 1
    I've updated title - feel free to revert. Note that "output" commonly used in context "console output" or all sorts of sequential logs/files. In UI case following words are more common: "update"/"refresh"/"show". – Alexei Levenkov Jun 12 '13 at 15:59
  • @AlexeiLevenkov Ah! That's a much better title. Thank you! – echolocation Jun 12 '13 at 16:05

5 Answers5

6

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());
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • Alright, I've been attempting this for awhile and have upgraded to .NET 5.0, but for some reason, Task.Run is not defined. `Error 1 'System.Threading.Tasks.Task' does not contain a definition for 'Run'` Am I using the wrong namespace? – echolocation Jun 12 '13 at 17:13
  • 1
    @BrianR There is no .NET 5. There is C# 4.0, and .NET 4.5. If you don't have a definition of `Task.Run` then it means you're using .NET 4.0, not 4.5. – Servy Jun 12 '13 at 17:18
  • @Servy Apologies, I am not at all familiar to C# and .NET. After changing the project properties to use .NET 4.5, your solution is working flawlessly. Thank you. – echolocation Jun 12 '13 at 17:22
  • @BrianR Don't worry about it; the versioning for C#, .NET, Visual Studio, etc. is all real messy. Being confused by it is perfectly understandable. – Servy Jun 12 '13 at 17:23
2

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

Justin Pihony
  • 66,056
  • 18
  • 147
  • 180
  • 1
    1) clarifying questions go in comments, not posts. 2) If you don't have a complete answer don't post an answer saying "More details to come." Post an answer when you have a complete answer. As it is, this is at best a comment. – Servy Jun 12 '13 at 15:36
  • No downvote, I just checked (update: now there is one). Also, I think this is better because it mentions `Task`s, which are **way, way** better than BGWorkers. – It'sNotALie. Jun 12 '13 at 15:36
  • Who deleted my comment about 4 minutes ago? my previous comment is `maybe that's just some mistake`? – King King Jun 12 '13 at 15:37
  • @Servy I will see if I can find something on meta, but I say that is BS. I see this methodology of answering ALL the time. You cannot deny that SO is in part a "quick to the draw" type of place. – Justin Pihony Jun 12 '13 at 15:42
  • @JustinPihony If you're going to post an answer it should *answer the question*. That's a fundamental concept of SO, and I can find a *lot* of examples on meta explaining why it's not appropriate to post comments, clarifying questions, or other non-answers as answers. That you intend to edit it into an appropriate answer doesn't make the initial post appropriate. What you seem to be confusing this with is posting a valid answer that answers the question, but doesn't go into a lot of depth, and for which the depth is added on in an edit. That is not a violation of the rules. – Servy Jun 12 '13 at 15:45
  • @Servy I did answer. I will say that my question should be a comment, so I will move that out. But I did not leave it at that. I elaborated from the start that the OP could use BGW or Tasks. That is a valid answer without going into depth...so, by your own words, "not a violation of the rules" – Justin Pihony Jun 12 '13 at 15:48
  • No, that alone isn't a valid answer. It's at best a comment. It doesn't meet the minimum standards for an answer. It's effectively a link-only answer. – Servy Jun 12 '13 at 15:50
2

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
1

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.

Knaģis
  • 20,827
  • 7
  • 66
  • 80
  • "In most cases" Except when it doesn't and your entire application crashes (or worse) because your abusing your message loop. – Servy Jun 12 '13 at 15:38
  • @Servy: In what scenarios would the app crash? – Douglas Jun 12 '13 at 15:39
  • how is this abusing the message loop (do you have some examples where it crashes the app)? it is just giving back the UI thread for a moment to process the messages. This is not the recommended approach but in some cases this might be more appropriate to implement than to switch to background processes. – Knaģis Jun 12 '13 at 15:39
  • 1
    @Knaģis It's important to use the function appropriately when using it, and this is not an appropriate use. In particular, it's important that when using `DoEvents` that it not be re-entrant; you need to disable the controls on the form that could be adding new events to the message queue. If you disable the whole form for the duration then it *can* work, and is just very bad practice. Fundamentally it's just very, very hard to actually get it to work right. It's *much* harder than the alternatives. If you don't know enough about how it works, you're better of not using it at all. – Servy Jun 12 '13 at 15:42
  • It should be noted that `DoEvents` does. If you haven't disabled all of the inputs then a user may click on another button or change some other state. As a rule it is better to disable all inputs before starting your long process and enable them when it is done. Exceptions would include a Cancel button or long running operations that effectively proceed as a separate batch that would not be affected by the user continuing to use the application. – HABO Jun 12 '13 at 15:47
0

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

tia
  • 9,518
  • 1
  • 30
  • 44