Multi-Thread Timeout challenges in C#

Recently I was dealing with a couple of bugs in C# regarding timeout behavior. The solutions were pretty interesting so I decided to share them.

Consider these scenarios:

  • We show a dialog to the user with some message. We close the dialog after 15 seconds. However, if the user closes the dialog manually, we have no need to close it at timeout.
  • Start a long operation. If that operation lasts more than 5 seconds – Abort it.
  • Whenever there’s a long operation in your App, we want to show an “In Progress” popup. We have no way of knowing how much time this operation will last, but it usually lasts less than a second. To prevent “blinks”, we want to show the popup only after 1 second. If, by that 1 second, the operation has finished, there’s no need to show the popup.

These problems are similar. We have to do operation X after a Timeout, unless Y happened during that time.

To find a solution to these problems, I created a small class for my experiments:

My operation:

My testing program:

The result should be:

Great, now we can start experimenting 🙂

Solution 1: Sleep on a different thread

My initial plan is to sleep on a different thread, and keep a boolean flag that changes if Stop was called.

Something like this:

After checking the regular scenario, this works well. But… something feels wrong, doesn’t it? Actually several somethings. For one thing, we waste a thread from the ThreadPool during the timeout. Then, if stopped, the thread continues to sleep until the end of the timeout, wasting both CPU time and a Thread.

But those are not the worst things about this code. We actually got a bug in our program 

What if, we start operation with 10 second timeout, stop at 2 seconds, and then start again in 2 seconds.

When starting for the second time, our flag _stopCalled will become false. Then, when our first Thread.Sleep() is finished, it will call on DoOperation, even though we cancelled it.
Afterwards, the second Thread.Sleep() is finished, and will call on DoOperation for the second time. Resulting in DoOperation being called twice instead of once.

If you have those timeouts 100 times a minute, you will have a hard time catching this kind of bug… trust me.

We need some kind of way to canceling the calling of DoOperation when StopOperationIfNotStartedYet was called.

How about try using Timer for this?

Solution 2: Start a Timer

I think there are 4 timers in .NET.  I even knew the differences once…
But, System.Threading.Timer is good enough for our needs.
So here’s the code using a Timer:

The result is:

Excellent. Now, when stopping the operation, the timer is disposed and there’s no danger of running the operation twice.

This will work pretty well, but… I want to look at another way to handle this.

Solution 3: ManualResetEvent or AutoResetEvent

Manual/Auto ResetEvent
AutoResetEvent and ManualResetEvent are classes that help you deal with multi-thread communication. The idea is that one thread can wait until another thread finishes some operation. Then, the waiting thread can be “released” and continue running.

mre.WaitOne() will wait until the manual reset event is signaled. mre.Set() will mark reset event as signaled. ManualResetEvent will release all threads currently waiting. AutoResetEvent will release just one thread waiting and immediately become non-signaled again.  

WaitOne() can also accept timeout as a parameter. If Set() was not called during the timeout, the thread is released and WaitOne() returns False. This will work great for us. Here’s the code using this functionality:

Result is:

I have to admit, I really like this solution. I think it’s cleaner than the solution we did using Timer. My personal opinion… but is it really better?
Well, dealing with our simple functionality, both ManualResetEvent and Timer solutions work well enough. So let’s get things more complicated 🙂

New Requirements Ahead

Let’s suppose we can now call StartWithTimeout() several times in a row, not waiting for the first timeout to finish.

But wait, what is the expected behavior here?
There are several possibilities:

  1. When calling StartWithTimeout during timeout of a previous StartWithTimeout:
    Ignore the second Start.
  2. When calling StartWithTimeout during timeout of a previous StartWithTimeout:
    Stop the initial Start and use the new StartWithTimeout.
  3. When calling StartWithTimeout during timeout of a previous StartWithTimeout:
    Invoke DoOperation for both starts. At StopOperationIfNotStartedYet, stop all operations not started yet (Within timeout)
  4. When calling StartWithTimeout during timeout of a previous StartWithTimeout:
    Invoke DoOperation for both starts. At StopOperationIfNotStartedYet, stop one random operation not started yet.

Possibility 1 and  can be easily achieved both with Timer and with ManualResetEvent. In fact, we already do this in our Timer solution

Possibility 2 can also be easily achieved. I won’t show you the code, but take as a challenge to do it yourself 🙂

Possibility 3 is impossible to achieve with the Timer solution as it is now. We will have need to have a collection of Timers. On Stop, we need to go over the list of timers and Dispose all of them. This is doable, but with ManualResetEvent we can achieve this very cleanly and easily!

Possibility 4 very much like possibility 3 can be achieved with a collection of Timers. But, we’re about to see a very clean solution using AutoResetEvent.

Possibility 3: A single ManualResetEvent to stop all operations

Let’s recap the challenge here.
Suppose we call StartWithTimeout with 10 seconds timeout.
After 1 second we call another StartWithTimeout with 10 seconds timeout.
After another 1 second we call another StartWithTimeout with 10 seconds timeout.

The expected behavior is for all 3 operations to start after 10 seconds, 11 and 12 seconds respectfully.

If, after 5 seconds we would call Stop(), then the expected behavior is that all operations pending would stop. Resulting in neither operation happening.

Let’s change our Program.cs a bit to be able to test this. Here’s the new code:

And here’s the solution using ManualResetEvent:

The output, as expected is:

Isn’t that awesome?

When I checked this, I was surprised that Thread.Sleep(10) was needed. But, without it, only 1-2 threads out of 3 waiting are proceeding. Apparently Reset() happens too fast, and the 3rd thread will stay on WaitOne().

Possibility 4: A single AutoResetEvent to stop one random operation

The challenge here is this:
Suppose we call StartWithTimeout with 10 seconds timeout.
After 1 second we call another StartWithTimeout with 10 seconds timeout.
After another 1 second we call another StartWithTimeout with 10 seconds timeout.

Then we call StopOperationIfNotStartedYet().
There are currently 3 operations at timeout, pending start. The expected behavior is for one of those to be stopped. The other 2 operations should start normally.

Our Program.cs can stay the same as before. The new code of OperationHandler is:

The result is:

Excellent, just as expected.

Summary

Doing some operation after a timeout is a  common problem. We saw some good solutions to this problem. When dealing with thread communication, a solution might look good enough and even work with a certain flow, but it can hide terrible bugs within. We need to be especially careful when this happens in real time. Maybe hundreds of times a minute.

AutoResetEvent and ManualResetEvent are very powerful classes. I use them all the time when dealing with Thread communication. Great stuff to keep in your toolbox.

Cheers,
Michael

2 thoughts on “Multi-Thread Timeout challenges in C#

  1. Moaid Hathot

    This is interesting. I’ll add a solution – Task parallel Library.

    The following method does exactly what you need:
    (full code: http://pastebin.com/K8MWQuJ6)
    public async Task DoWithTimeout(Action action, Action timeoutAction, TimeSpan timeout)
    {
    var cancellatinSource = new CancellationTokenSource();

    var task = Task.Run(action, cancellatinSource.Token);

    if (await Task.WhenAny(task, Task.Delay(timeout)) != task)
    {
    cancellatinSource.Cancel();
    timeoutAction();
    }
    }

    Example:
    public async Task Test()
    {
    await DoWithTimeout(() =>
    {
    Console.WriteLine(“Starting main action…”);
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(7));
    Console.WriteLine(“Finishing main action”);
    },
    () => Console.WriteLine(“Timeout action is invoked”),
    TimeSpan.FromSeconds(5));
    }

    Reply
    1. michaels9876@gmail.com Post author

      Thanks Moaid, looks like a very good solution!
      I’m using .NET 4 for a very long time now and less familiar with Async await.

      This seems cleaner and more explicit than the Timer solution and the ResetEvent solutions.

      You will be able to deal with possibility #3 if using the same instance of CancellationToken.
      Though to handle possibility #4 we still need AutoResetEvent or a collection of Tasks.

      Reply

Leave a Reply