Massimo
Massimo

Reputation: 1540

How to handle control updates while doing processing?

I'm writing a small .NET (Windows Forms) application, but I can't seem to get the multithreading right.

The application has a very simple logic: it displays a main form with some parameters to fill in, a button to start processing and a progress bar; processing starts when the user clicks on the button, the progress bar should be updated while the program runs, some MessageBoxes can pop up while processing in order to display warnings/informations, and when processing ends another MessageBox pops up with the results. Fairly simple... but I can't seem to find a way to handle control updates correctly.

First I tried the simple route: doing all of my processing in the Button_Clicked event. This didn't go well: it worked, but the form became completely unresponsive while processing, it didn't care about any user click at all. I assumed this was because it was actually busy handling an event, thus stopping processing other ones... so I went with multithreading.

In the button handling event I started a new Thread and made it do the processing, and then called Thread.Join on it; MSDN documentatin says about Thread.Join: "Blocks the calling thread until a thread terminates, while continuing to perform standard COM and SendMessage pumping" (http://msdn.microsoft.com/en-us/library/95hbf2ta.aspx). But this doesn't seem to be the case: after calling Thread.Join, the form became unresponsive again, until the thread terminated. Why is this? I ditched the Join idea and immediately returned from the event handling function (disabling buttons on the form to avoid users starting more than one processing at the same time), but then I met another problem: I started getting exceptions when trying to update the progress bar, the runtime complained about having called a control from a thread that was not its owner.

So I finally went with a BackgroundWorker: I added one to the form, called its RunWorkerAsync() method and used ReportProgress() to fire a ProgressChanged event and handle it in the main form (to avoid the cross-thread control update problem). I was finally able to update the progress bar... but now this is completely asynchronous: the progress bar is actually updated at some random interval after the worker thread calls ReportProgress() (even one second!), and this gives a particularly ugly visual effect. Two examples:

What am I missing here?

Upvotes: 2

Views: 235

Answers (3)

Benjol
Benjol

Reputation: 66541

Well you've tried quite a few things. Here are a few tips, maybe not the right answer for you, but could help you.

To overcome the "updating control from other thread" problem, you can use the Invoke or BeingInvoke commands on the control (I use an extension method for this).

That aside, I think that Kangkan's comment is legitimate - using a MessageBox is just adding pain to your problem, because it is modal and it blocks the UI from updating. Are you SURE that the MessageBox is appropriate - do you absolutely need user input before continuing? Or do you just need to inform them? If the latter, you should consider using something more like a status bar, or the system messages you get here on StackOverflow (imagine if they were all alerts!)

Upvotes: 0

Jaster
Jaster

Reputation: 8581

add Application.DoEvents() to update the UI where it is needed.

Upvotes: 0

Myles McDonnell
Myles McDonnell

Reputation: 13335

By far the best way, IMHO, to handle asynchrony in WinForm (and WPF) apps is Reactive Extensions. Bit of a curve to go through but once you get it you won't go back.

Upvotes: 0

Related Questions