threading newbie

D

Dan

I'm new to multithreading and I'd like to ask a generic question about
application structure. The scenario is:

- a winforms c# application serves as UI to expose the functions of an
'engine' class to the user. The engine class is completely self-contained
and is a data member of the UI.

- when the UI launches a lengthy process executed from the engine, it should
update various controls (e.g. a listview with some logging) to reflect the
progress of the operations; further, the user must be able to abort the
process at any moment.

The only purpose of multithreading here is to keep the UI responsive and
updated during a lengthy process. Here's how I implement this:

- fields of UI class (a Form-derived class) are the engine itself (let's
call it _engine) plus a Thread (say _tEngine), a progress window form and a
flag named _bWorking.

- when the user launches the lengthy operation, this flag is set to true and
the progress form is created and shown (modeless); I then create a delegate
for the thread and the thread itself, e.g.
ThreadStart worker = new ThreadStart(_eng.DoSomeWork);
_tEngine = new Thread(worker);
_tEngine.Start();
Then this function exits.

- the engine fires some events to notify the client about its progress; the
UI has subscribed to these events so that when handling them it can display
some data into its controls (data are passed back as event arguments, where
any reference object is a clone of the original).

- when the engine has finished, it fires an 'all-done' event which is
handled by the UI to reset the _bWorking flag, close the progress window and
destroy it.

- if the user cancels the process by closing the progress window, the window
fires a Cancel event which is handled by the UI class which calls
_tEngine.Abort(), resets the working flag and destroys the progress window.

I've created a dummy application do test this scenario and it seems to work
fine: the engine keeps working while the UI is responsive and is updated
continuously, until it ends or the user cancels. Anyway, before implementing
it in my real-word application I'd like to know if this is ok or I'm doing
something wrong which could be disastrous...

Thanks to all!
 
A

AlexS

To me seems fine, except that I would refrain from using Abort. Especially
if thread could be restarted.
Main point is - you have now only one thread related to UI, not many, right?

HTH
Alex
 
D

Dan

Thanks Alex! I have only 1 thread related to UI: really, the whole
application is single-threaded, and I'd simply like to start a worker thread
for this lengthy computation. Anyway, how can I let the user stop the
processing without using Abort? (in any case, the thread will not be
restarted when aborted or failed; it will be re-created if the user reissues
the same command).
 
J

Jon Skeet [C# MVP]

Dan said:
Thanks Alex! I have only 1 thread related to UI: really, the whole
application is single-threaded, and I'd simply like to start a worker thread
for this lengthy computation. Anyway, how can I let the user stop the
processing without using Abort? (in any case, the thread will not be
restarted when aborted or failed; it will be re-created if the user reissues
the same command).

The normal way is to have a flag which is checked on a regular basis,
and set it from the UI if the user wishes to cancel the operation. You
should check and set it in a thread-safe way - either use a volatile
variable, or do both checking and setting within a lock.
 
D

Dan

Thanks again... I have another dummy question: while expanding my test
application (see previous posts) I added some UI cleanup to respond to the
'end processing' event fired by the engine class once it has finished. The
problem is that the handler of this event throws an exception telling that
"'Controls created on one thread cannot be parented to a control on a
different thread", i.e. the thread which should cleanup the UI is the engine
thread, not the UI thread which owns the controls. So how should I manage
such situations?
 
J

Jon Skeet [C# MVP]

Dan said:
Thanks again... I have another dummy question: while expanding my test
application (see previous posts) I added some UI cleanup to respond to the
'end processing' event fired by the engine class once it has finished. The
problem is that the handler of this event throws an exception telling that
"'Controls created on one thread cannot be parented to a control on a
different thread", i.e. the thread which should cleanup the UI is the engine
thread, not the UI thread which owns the controls. So how should I manage
such situations?

Use Control.Invoke to run a delegate on the UI thread. Anything which
touches the UI should be run on the UI thread.
 
W

William Stacey [MVP]

Many ways to go here and yours sounds like it works. However, I favor a
simpler approach with also lends itself to be leveraged with other forms or
other ui like console.
1) Create a class that contains your thread and the thread start delegate as
a private method. Now your worker is contained in a class - call it Worker
or what ever. During construction, the thread is created but not started
and any other setup.
2) Create at least a Start and Stop public method on the class. Sync the
enum using a lock because it will be used by at least two threads (i.e. your
worker and your UI caller);
3) Create a stats struct and give it a public property in your worker class.
The worker will lock on your synclock object before updating this struct.
Your public property will lock on sync object and return the struct.
4) Now in your form, create a private ref for the Worker class and create an
instance in form load or other. Call the Start() method when you want it to
start.
5) Use a form timer (or a Refresh Button) to poll the stats struct from the
Worker object and update any form elements.
6) If element in stats struct says done, then stop the form timer and do any
completed updates. To allow UI to stop the worker, call the Stop() method.

In my mind, this is clean. Your worker does not need to know about any form
elements or other delegates, it just does its thing and posts stats to its
struct or vars. Your form just polls for updates and can control the worker
via stop and start methods. As your worker is UI agnostic, you can use it
in a console app, remoting, a win service, etc.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top