Fog Creek Software
Discussion Board




Welcome! and rules

Joel on Software

Multithreading design question

I'm new to writing multithreaded apps and I have a design question.  I have a winforms app and a class which has a method that does processing which is time intensive.  I want the user to be able to kick off the process and continue to work in the appliaction while getting progress updates and the ability to cancel.  The method that seems easiest to me is this:

The class exposes certain events for progress. Start, ProgressUpdate, and End.  Create an EventArgs class for passing data to the handlers.  The form has an instance of this class with handlers for these events.  When the user clicks go, a new thread kicks off that calls the method on the class to do processing. 
The method in the class raises the events at the appropriate time.  The ui thread traps those events and updates the UI accordingly.  The processing method in the class checks a variable after each chunk of processing to see if the user wants to cancel and raises the ProgressUpdate event.  If the user clicks cancel in the form, the cancel member of the class instance is set to indicate the user's request and execution ends after the latest chunck of processing is complete.

Since I'm new to multithreading, I'm unsure if this is a decent way to make everything work.  Is this a solid design or am I commiting horrible crimes against the multithreading gods?

Paul Dix
Wednesday, June 22, 2005

==>Is this a solid design or am I commiting horrible crimes against the multithreading gods?


Can't answer to that. I'm atheist and don't know of these "multithreading gods" <grin>.

I'm fairly new to multi-threading -- just started about 4 months ago on a new project. What you outline is effectively the way we do it (with some minor differences), and we're having no problems. There are 2 caveats I'd like you to be aware of, though:

(1) You'll want to look at Invoke, BeginInvoke, and InvokeRequired. You'll have to have your UI updating come back in on the main UI thread. There's plenty of examples in the WinForms docs.

(2) If your long-running thread is a data access thread, there's really no good way to kill it. We run into a situation where we send off a query to SQL Server and the thread does not respond until the SQL Server returns its results. It's annoying, but I guess it's OK, cause that's the way some of the SQL tools (QA in particular) work. You can attempt to cancel the query, but sometimes it won't cancel until it's done. Just a quirk of SQL Server, I guess. In these cases, we mark it (with a boolean) as a dead thread until SQL Server returns and the thread can be cancelled, or, in the worst case, hard kill the thread with Abort when the user exits the app. Haven't really found a good, elegant way around this one yet, but what we do with it works for our purposes.

Sgt. Sausage
Wednesday, June 22, 2005

paul i just backordered MultiThreading in .NET.

If you get an answer to this question, then please hit me up. I'm very curious.

Patrick
Wednesday, June 22, 2005

.NET 2.0 will implement a new class called BackgroundWorker which will help with these types of issues. Until then, I have seen several people write equivalent classes for .NET 1.1. You might want to look at how they do it. Here is a reference to one such project.

http://www.vsdotnet.be/blogs/tommer/PermaLink,guid,1203f3ce-cb7b-42d8-b9fa-56c190e3cdb7.aspx

matt
Wednesday, June 22, 2005

Sgt. Sausage,
I have seen the documentation about Invoke.  I saw that it was a cardinal rule to only update the UI from its thread.  I thought I was clear of these issues because I have the event handler in the main UI thread.  Is this not the case because I'm raising the event from a worker thread?

Luckily, for what I'm doing number 2 isn't an issue.

Paul Dix
Wednesday, June 22, 2005

Another point is that I would use interfaces to decouple your background task from the UI.  I do something along the following lines:

- Define an interface e.g. IProgress with a method e.g. "ReportProgress"  which takes arguments which describe the progress (steps completed, total steps, a message to display, ...) and returns a boolean which indicates if the user has cancelled.

- Define an interface ITask with a method something like
"bool Execute(object taskData, IProgress progress)"

- Background tasks are implemented by classes which implement the ITask interface.  The Execute method starts the long running task, calling the IProgress.ReportProgress method after each chunk of work to report progress and check if the user has cancelled (in which case it returns false).

- Create a form with a UI to report progress (e.g. a progress bar and a label with a message from the background task), this form implements IProgress in the obvious way.

- The progress form can have a static method:
bool ExecuteTask(ITask task, object taskData).

This method displays the progress dialog modally, then calls ITask.Execute, and returns true if the task completed successfully, else false if it was cancelled.

If the long running task keeps the message pump running (DoEvents), there may be no need for a separate thread.  If you do need a separate thread (e.g. long running db queries) then don't forget about using Invoke where necessary as others have pointed out.

Joe
Wednesday, June 22, 2005

==>I thought I was clear of these issues because I have the event handler in the main UI thread.  Is this not the case because I'm raising the event from a worker thread?


What do you mean by: "because I have the event handler in the main UI thread" ?

How, exactly, did you get it there -- on the UI thread?

Just because the code lives in a form or mixed in with some other code in your UI does not mean that it's called on the UI thread.

Sgt. Sausage
Wednesday, June 22, 2005

Well the code for the handler is defined in the code of the form.  I realise that doesn't mean that it's executed in that thread.  That's part of where my understanding trails off.  How events work in a multithreaded app?  If I raise the event in the worker thread.  Does the handler execute in the worker thread or does it execute in the UI thread?

Paul Dix
Wednesday, June 22, 2005

==>If I raise the event in the worker thread.  Does the handler execute in the worker thread or does it execute in the UI thread?

Again, because the code for the handler exists in the form does not mean it will execute on the UI thread. It will execute from whatever thread you're raising it from, which means that if you don't Invoke or BeginInvoke on the UI thread, it's going to execute on whatever thread you're calling it from.

Now this, in and of itself is not a bad thing. Your handler *can* execute on your thread instead of the UI thread. But ... if your handler touches controls on the form or touches the form itself, you'll want to Invoke/BeginInvoke those parts of your handler (or, alternatively, the entire handler if it touches a lot of the UI)

Here's a simplistic example that demonstrates Invoking events on the main UI thread. You'll likely have to adapt it, but it's a simple example:

http://www.codeproject.com/csharp/workerthread.asp?df=100&forumid=2441&exp=0&select=427054

Sgt. Sausage
Wednesday, June 22, 2005

Paul,

Chris Sells has the definitive set of articles on *exactly* what you're trying to do.

Part 1:
http://tinyurl.com/29sd

Part 2
http://tinyurl.com/4asg

Part 3:
http://tinyurl.com/837q8

Mark Pearce
Thursday, June 23, 2005

Chris Sells also put together a good article, ".NET Delegates: A C# Bedtime Story"

Patrick
Thursday, June 23, 2005

*  Recent Topics

*  Fog Creek Home