tags:

views:

1175

answers:

10

I have a WinForms app written in C# with .NET 3.5. It runs a lengthy batch process. I want the app to update status of what the batch process is doing. What is the best way to update the UI?

A: 

Application.DoEvents() or possibly run the batch on a separate thread?

Adam Driscoll
I would use BackgroundWorker and have the batch run on a separate thread. I don't like Application.DoEvents() because even if you are doing that, while you aren't doing that and you're processing work, your UI is no completely unresponsive and can appear "hung" to your user.
jolson
Don't do this-- it's a real code smell. I've never seen an `Application.DoEvents()` in production code where I thought, "That was the easiest, most correct solution."
Greg D
If you use CLR Profiler 2.0, you will notice that using DoEvents will create a LOT of handles, which stick around! I would suggest using BackgroundWorker like jolson said.
joek1975
+12  A: 

The BackgroundWorker sounds like the object you want.

Austin Salonen
+1  A: 

Use the backgroundworker component to run your batch processing in a seperate thread, this will then not impact on the UI thread.

benPearce
+3  A: 

Run the lengthy process on a background thread. The background worker class is an easy way of doing this - it provides simple support for sending progress updates and completion events for which the event handlers are called on the correct thread for you. This keeps the code clean and concise.

To display the updates, progress bars or status bar text are two of the most common approaches.

The key thing to remember is if you are doing things on a background thread, you must switch to the UI thread in order to update windows controls etc.

Chris
+3  A: 

The quick and dirty way is using Application.DoEvents() But this can cause problems with the order events are handled. So it's not recommended

The problem is probably not that you have to yield to the ui thread but that you do the processing on the ui thread blocking it from handling messages. You can use the backgroundworker component to do the batch processing on a different thread without blocking the UI thread.

Mendelt
A: 

DoEvents() was what I was looking for but I've also voted up the backgroundworker answers because that looks like a good solution that I will investigate some more.

Guy
Definitely consider the BackgroundWorker -- every time I've encountered DoEvents(), it's never been fun to fix subsequent issues.
Austin Salonen
+1  A: 

I want to restate what my previous commenters noted: please avoid DoEvents() whenever possible, as this is almost always a form of "hack" and causes maintenance nightmares.

If you go the BackgroundWorker road (which I suggest), you'll have to deal with cross-threading calls to the UI if you want to call any methods or properties of Controls, as these are thread-affine and must be called only from the thread they were created on. Use Control.Invoke() and/or Control.BeginInvoke() as appropriate.

Martin C.
+1  A: 

If you are running in a background/worker thread, you can call Control.Invoke on one of your UI controls to run a delegate in the UI thread.

Control.Invoke is synchronous (Waits until the delegate returns). If you don't want to wait you use .BeginInvoke() to only queue the command.

The returnvalue of .BeginInvoke() allows you to check if the method completed or to wait until it completed.

Bert Huijben
A: 

Here's an alternative to BackgroundWorker (plug).

rosenfield
+1  A: 

To beef out what people are saying about DoEvents, here's a description of what can happen.

Say you have some form with data on it and your long running event is saving it to the database or generating a report based on it. You start saving or generating the report, and then periodically you call DoEvents so that the screen keeps painting.

Unfortunately the screen isn't just painting, it will also react to user actions. This is because DoEvents stops what you're doing now to process all the windows messages waiting to be processed by your Winforms app. These messages include requests to redraw, as well as any user typing, clicking, etc.

So for example, while you're saving the data, the user can do things like making the app show a modal dialog box that's completely unrelated to the long running task (eg Help->About). Now you're reacting to new user actions inside the already running long running task. DoEvents will return when all the events that were waiting when you called it are finished, and then your long running task will continue.

What if the user doesn't close the modal dialog? Your long running task waits forever until this dialog is closed. If you're committing to a database and holding a transaction, now you're holding a transaction open while the user is having a coffee. Either your transaction times out and you lose your persistence work, or the transaction doesn't time out and you potentially deadlock other users of the DB.

What's happening here is that Application.DoEvents makes your code reentrant. See the wikipedia definition here. Note some points from the top of the article, that for code to be reentrant, it:

  • Must hold no static (or global) non-constant data.
  • Must work only on the data provided to it by the caller.
  • Must not rely on locks to singleton resources.
  • Must not call non-reentrant computer programs or routines.

It's very unlikely that long running code in a WinForms app is working only on data passed to the method by the caller, doesn't hold static data, holds no locks, and calls only other reentrant methods.

As many people here are saying, DoEvents can lead to some very weird scenarios in code. The bugs it can lead to can be very hard to diagnose, and your user is not likely to tell you "Oh, this might have happened because I clicked this unrelated button while I was waiting for it to save".

Niall Connaughton
wow, how did this become active again? I guess I should read the post dates before writing lengthy answers! Still, might help someone...
Niall Connaughton
+1 for good detail.
Greg D