Multithreading traps in client-server application

Multithreading traps in client-server application

Multithreading in WinForms using Thread and backgroundWorker in a client-server scenario.

Let's say we have a client application that allows the user to run a procedure on the server that takes a long time to execute and displays progress both by printing text in a listbox control and by showing activity as a progress bar.

Let’s start with a simple form with one button.

 

Clicking the run button will just disable it, activate the progress bar and run the long procedure.

Long procedure is a simple Thread.Sleep.

 What's the result ? The application gets stuck and becomes unresponsive. The progress bar doesn't show up.

Why is that ? It's because both Form and ServerClass.RunLongProcedure is processed by the same thread which gets blocked by the Sleep command. 

 Let's separate them by using Background Worker, a standard WinForms component.

Now RunLongProcedure will be executed within a new thread, the progress bar shows up and is moving from left to right automatically (style is set to Marquee).

 What we need now is for the RunLongProcedure to be able to be aborted and return real progress. The progress bar is set to Blocks style and its initial value is set to 0.  We have to set up the background worker so that it supports progress changes and allows cancellation.

Here we run into the first problem - how to report progress of something we have no knowledge of at the client side.

Our DoWork function does not do any real job. It serves as a separate thread allowing the progress bar to really progress and the Abort button to be clickable. The real job is done at the server side. So the ServerClass is the object that "knows" what the progress is and can report it. ServerClass has to inform of the progress in some way. One of the possible ways of doing so is to use dependency injection and pass an interface of our Form1 class to ServerClass.

 

Our long running method has to change its form to accept Iclient interface and report progress.

Let's add method to Form1:

 Now let’s run our app. What happens when we click the Run button ? An exception is thrown. What happened is very common when building multithreaded client application – the ServerClass object is trying to manipulate the progressBar1 control of the Form1 object, which is not allowed because the object and the control are running on separate threads.

Manipulation of controls between threads is not allowed directly.

backgroundWorker offers a very easy to use mechanism to overcome this problem - ProgressChanged event. Any progress and state information can be passed to this event and it allows backgroundWorker to update any control included in Form1. In our case we can change the progress on the bar and add a new value to the listbox.

Now progress is reported in two ways - textual and graphical. Nice.

 

At this point an Abort button should be quite easy to implement. On the server side we're passing an order to abort whenever it's possible and the server decides how to abort. From the server’s side:

 

And from client side:

So clicking the abort button sends a signal to the server that it has to stop. When the server runs into the "if (aborted)" line of the code, it breaks the current "for" cycle and returns. All is fine and too easy ;) Typical client-server system works a bit different and we have to simulate it by separating Form1 from backgroundWorker  and ServerClass by running it in another thread. Yes, a third thread. This way our progress bar can function even when there are delays in the client-server connection. Also after sending the RunLongProcedure signal to the server, the client will not freeze while waiting for a response.

 

We have to change the parameter of RunLongProcedure into an Object because this is the only way it would be accepted by the Thread object. Our Iclient interface can be passed as an argument of the Thread.Start method.

Let's actually run this application, what happens ? It does not show any progress and immediatelly throws an error that it can't report progress after clicking OK in the "Finished" MessageBox . Why ? because backgroundWorker is no longer connected to the server and finishes work just after starting RunLongProcedure. This is not what we wanted.  

What we need is to know when to close backgroundWorker !

As always, there are several ways to do that. Let's explore checking IsBusy property of ServerClass. Background worker will periodically check if the server is still busy. If so, then it's going to sleep again. Until it's not busy. Server:

Client:

Let's run this. What's the result ? Again the same error ! What's wrong ?

We have to analyze steps:

  1. Starting RunLongProcedure thread
  2. Checking IsBusy property immediatelly returns false !

On your computer it may run just fine, on another computer it may fail. It's because creation of a new thread typically takes more time than moving to another line of the code  - while (ServerClass.Current.IsBusy)

The easy workaround is to put another sleep in between:

Typically the program will run properly because the thread will already be in the busy mode, returning IsBusy=true.

There are several traps you can fall into when using Sleep command.

First of all you can never assume that one or another sleep parameter will be suitable for some situation. On one computer 1000 may be too much, on another you may need 1100 miliseconds. Or 300.

No sleep parameter is good enough

Sleep in while loop is not harmful, however it's not very efficient either - you have to wait for the sleep time you declared to pass. In a complex system with thousands of calculations you should not make application doing nothing most of the time...

Moreover using constant parameters (so called magic numbers) may mean unstable application on the customer site due to the number not fitting local requirements. The very same application that worked perfectly on your site !

One solution to this, which I personally prefer, is to use AutoResetEvent object offered by .Net Framework.

resetEvent will wait until it's released. It's a great asynchronous tool.

Let's run the application. It reports progress as it should. But it does not end processing !

The reason for this is that for the process to end, the server needs to communicate to the client that it’s finished the job. This can be done by implementing another method in the old Iclient interface:

RunLongProcedure informs the client application form that it’s finished a job using ProcessingStop. If the application is to be properly encapsulated, the server should not manipulate the resetEvent property directly. Form1 knows better how to stop itself. The ProcessingStop method sends a signal to the backgroundWorker that the resetEvent object can be released and WaitOne method can proceed.

The application looks fine. But still there are a lot of problems with it. First of all it is a very bad habit to let fragments of other applications or even whole applications to assume total control of our client. In this case, the line resetEvent.WaitOne() controls client application in a way that would cause it to freeze forever in case of server failure. Let's fix it.

 

Always be prepared to use timeouts, and make them editable in the config file

Timeout settings are passed to the WaitOne method, which can now pass the information if the timeout has expired or not. If there is a problem, Failure can be passed to the e.Result object provided by the backgroundWorker. In order to use it we have to modify the RunWorkerCompleted method.

Note that we are not passing a simple boolean result that we get from WaitOne method as it is a bad habit to use boolean in such cases: you may want to pass more than two different results. Also, true does not mean success or failure in the case of e.Result object. Boolean could be used if it was an e.IsSuccess object.

Of course you may get timeout if the timeout property is set to a too short period of time, but it will not be the server that will return an error, potentially causing a complex process to crash. The process itself will continue unbothered. Just make client timeout longer. You may now think what would happen if we timed out the client before the server actually finishes its job. This is the result:

The exception is thrown because backgroundWorker finished working after the timeout period expired and it no longer allows any report progress. An easy way to fix this:

We have two more problems to handle: Abort and closing the client. The first problem is that the Abort button works only partially. It stops returning progress but does not finish backgroundWorker or release client application. Here is the source of the problem:

The server gets the Abort signal from the client and finishes the job. The client, however, works asynchronously and does not know this. You could change this line into:

But I recommend you don't :)

Follow  DRY principle. Dont Repeat Yourself.

 

We've changed the app to be more secure by proactively checking if the Iclient object was provided, also we're making sure that we pass ProcessingStop to the client in every possible scenario.

Make sure that any code in the finally block is safe as it may blow up your entire application.

In this case we check if the client is not null which would cause the application to crash. The ProcessingStop method may throw an error as well – it should be examined separately but this is a subject for another article.

You could also ask why we didn’t use thread.Abort here:

 

Avoid Thread.Abort if possible. You never know which line of your method’s code is processed when the abort occurs !

It may occur within try block, but also within catch and finally blocks ! Also Abort aborts not only this method but any other dependent methods so you have no control over what's going on. Use Abort only as a last resort.

Never let application die without any control

Now let's focus on closing Form1. Releasing and disposing of objects is one of the most often omitted and forgotten functions in c# programming. In our case it's also letting application die instead of releasing important components in a peaceful manner. In some situations letting the application close itself  may lead to exceptions - the server side would try to update the client's progress while some client components would no longer be available.

In this example we want to abort server processing and then close the client. Also we want backgroundWorker to NOT show Finish/Failure message, by using private ForceClosing property.

Abort will issue abort on the server side as well, but will not wait for any reply from server (in bad situation server may be aborting for several seconds or even minutes) because we don't care (in this scenario). We're making sure that interface methods do not throw errors if they are run by the server after backgroundWorker had been forcefully closed.

 

Just to make sure there is no signalling method for any threads after closing application let's dispose of the resetEvent object:

If you really want the client app to wait some time for the server, you may do so, but don't make the user wait for it with his application blocked. Let's make sure backgroundWorker closes (by setting resetEvent), even if the server fails and the application hides just after clicking the Close button.

 

>>> Full Source for download may be found here <<<

 Regards

Dominik Steinhauf
CEO, .Net developer, software architect
www.indesys.pl

If you need help with your software project, or need customized software for your company, contact me at: dominik.steinhauf@indesys.pl

 

Alfonso P.

IT Manager | Technical Lead | ITIL®

8y

Thanks for another interesting post! Multithreading is extremely useful and a must, specially when heavy processes are involved (like large database transactional operations) but, as you clearly set, preventing out-of-control-threads is critical.

To view or add a comment, sign in

Insights from the community

Explore topics