Windows Form Threading

  • Thread starter Thread starter Fred Strauss
  • Start date Start date
F

Fred Strauss

I have a question regarding multi-threading in a Windows Forms
application.

I am writing a simple application to merge together a group of
files. I have created a class that holds the list of files and
contains a method to actually merge the files together.

The list is built and manipulated using the provided user interface.
In order to provide progress updates while the merge operation is
taking place I have created 3 events the merge object will fire as the
merge proceeds. The reason for the events is I did not want the merge
object to have any intrinsic knowledge of the UI components being
updated

I have set up event handlers on my form to handle these events and
update the UI. Specifically the handlers update a progress bar and
label control on the form. This sort of works when the Merge() method
is invoked on the UI thread in that I see the progress bar updates but
not the label update. I suspect this is because the Merge() method
never allows the UI to be updated properly while it's running on the
same thread.

So...I would like to be able to invoke the merge method on a new
thread. I realize I can do this by creating a delegate for the Merge()
method and using BeginInvoke() to call it. However, I also understand
it's not kosher to update UI elements on anything but the UI thread so
I'm wondering what happens when the Merge() method called on the
non-UI thread starts firing events?

Are the registered event handlers invoked on the thread the Merge()
method is running on or on the UI thread?

Is invoking an event handler from a UI object on a non-UI thread OK?

If they are invoked on the non-UI thread how should I go about
updating the UI elements. Do I need to create an UpdateUI method and
call it using Invoke()?

Thanks for any help!

Fred
 
Fred,

You are right about why your label is not updating when you are merging
the files on the UI thread.

Your event handlers can be called from any thread. There is no
limitation here. However, if the code in your event handlers performs any
UI operations (and most do), then you will need to call Invoke on the
control/form to call a method on the thread that created the UI. Basically,
set up a method that will update the progress bar and label (your UpdateUI
method), then create a delegate with the same signature. In your event
handlers, create the delegate, and pass it to Invoke, along with any
parameters.

Hope this helps.
 
How about flipping this a bit and simplify at that same time. Have your
merge object on another thread as you do now. Create a public property on
your merge object that will return a stuct or class with required statisics
you want to see in the form (you need to handle sync issues on any shared
vars or come up with some copy scheme to your shared stats struct.) Create
a timer on your form with the resolution you need. The timer method will
get the stat struct from the merge object using the public property. Now
you can do what ever you want with the stats to update your form. Your
merge object has no knowledge of any gui elements or callbacks or events so
you can use it naturally from different forms or console apps or services or
web service objects, etc.
 
How about flipping this a bit and simplify at that same time. Have your
merge object on another thread as you do now. Create a public property on
your merge object that will return a stuct or class with required statisics
you want to see in the form (you need to handle sync issues on any shared
vars or come up with some copy scheme to your shared stats struct.) Create
a timer on your form with the resolution you need. The timer method will
get the stat struct from the merge object using the public property. Now
you can do what ever you want with the stats to update your form. Your
merge object has no knowledge of any gui elements or callbacks or events so
you can use it naturally from different forms or console apps or services or
web service objects, etc.

That's an excellent idea.

However, I do want to be able to indicate when the merge has actually
ended. I suppose this can be done using a state property or would a
combination of an "EndMerge" event and these status properties be the
way to go?

Fred
 
Fred,

You are right about why your label is not updating when you are merging
the files on the UI thread.

Your event handlers can be called from any thread. There is no
limitation here. However, if the code in your event handlers performs any
UI operations (and most do), then you will need to call Invoke on the
control/form to call a method on the thread that created the UI. Basically,
set up a method that will update the progress bar and label (your UpdateUI
method), then create a delegate with the same signature. In your event
handlers, create the delegate, and pass it to Invoke, along with any
parameters.

Hope this helps.

Thanks for the response. It was just what I needed.

Fred
 
Fred,

You are right about why your label is not updating when you are merging
the files on the UI thread.

Your event handlers can be called from any thread. There is no
limitation here. However, if the code in your event handlers performs any
UI operations (and most do), then you will need to call Invoke on the
control/form to call a method on the thread that created the UI. Basically,
set up a method that will update the progress bar and label (your UpdateUI
method), then create a delegate with the same signature. In your event
handlers, create the delegate, and pass it to Invoke, along with any
parameters.

Hope this helps.

Thanks for the response.

It's just what I was after.

Fred
 
I would just make it a Completed bool in your struct. Keep polling until
Completed, then turn off your timer to stop the timer events.
 
How about flipping this a bit and simplify at that same time. Have your
merge object on another thread as you do now. Create a public property on
your merge object that will return a stuct or class with required statisics
you want to see in the form (you need to handle sync issues on any shared
vars or come up with some copy scheme to your shared stats struct.) Create
a timer on your form with the resolution you need. The timer method will
get the stat struct from the merge object using the public property. Now
you can do what ever you want with the stats to update your form. Your
merge object has no knowledge of any gui elements or callbacks or events so
you can use it naturally from different forms or console apps or services or
web service objects, etc.

William, I just noticed that your reply may have some relevance to a
query I just posted (only in the csharp group). Subj:
"Invoke vs .... (for thread communication with UI controls"
Would you mind taking a look at that post?

It sounds like you are referring to use of shared variables, with a
timer in the UI thread set up to poll for new data. I'm curious about
whether that approach would work if the background thread was writing
to a DataTable that is bound to a DataGrid control. I'm not sure how
the automatic update mechanism works for DataGrids or whether there
would still be interthread marshaling problems.
 
Your grid is still just a control on your form, so you can work with it on
the UI thread safely. The Windows.Form.Timer control gives you the ability
to work on the UI thread. So its a consumer/producer issue after that.
Your timer method will consume data and update the grid on the UI thread.
Your producer would need to be changed (if you want to use this method) to
not update anything directly, but just produce data. You can use an object
model to produce the data (i.e. class, struct with fields/properties for
each data column of data), or multi-array, etc. The point is your producer
need not know anything about forms or grids or controls or delegates to
forms, etc. It creates data and puts it in a format your consumers can use
easily (maybe an array of structs or classes.) If your producer just puts
together a glob of data and is done, then polling from a form.timer may not
be the best method to use as you will be polling for just one event to
happen. If that is the case, you may want to wait on an event in the
producer class using another thread or use the Invoke method to call a
method of your form that updates the form on the UI thread as others have
said.

This made me think of some sugar that could be added to Forms programming to
make this situation extremely easy. Decorate a form method with a [WaitOn
UIMethod="dosomething"] attribute or something This method will be
automatically started on another thread and you wait on some event in that
method and return true when done with your wait. It will then automatically
run the UIMethod on the UI thread. Something like:

[WaitOn UIMethod="UpdateMyForm"]
private bool MyWait()
{
myproducer.Event.WaitOne();
return true;
}

private void UpdateMyForm()
{
mydata = myProducer.Data;
this.textbox.text = mydata.name;
///etc.
}

Need to workout a few issues such as what method to use to start the wait
(maybe MyWait.StartWait()) and passing data between the two methods (like
Iasync or something.)
 
And lets not forget the simplest solution of all...

If you do perform the file merge (or any operation that takes a little
time) on the main UI thread, and you are happy with that, you can
always stick an

Application.DoEvents();

into your event handlers (or wherever you like).
This will give the UI a chance to refresh all of it's controls.

Obviously for seriously long lived operations, or for blocking
operations, a multithreaded approach with correct use of Invoke() is
better.


Simon
 
Back
Top