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:

public class OperationHandler
    {
        private IOperation _operation;

        public OperationHandler(IOperation operation)
        {
            _operation = operation;
        }

        public void StartWithTimeout(int timeoutMillis)
        {
             //Should call "_operation.DoOperation()" after timeout without freezing the thread
        }

        public void StopOperationIfNotStartedYet()
        {
            //Should stop "DoOperation" from being called if during timeout
        }
    }

My operation:

public class MyOperation : IOperation
{
    public void DoOperation()
    {
        Console.WriteLine("Operation started");
    }
}

My testing program:

static void Main(string[] args)
{
    var op = new MyOperation();
    var handler = new OperationHandler(op);

    Console.WriteLine("Starting with timeout of 5 seconds");
    handler.StartWithTimeout(5 * 1000);

    Thread.Sleep(6 * 1000);

    Console.WriteLine("Starting with timeout of 5 but cancelling after 2 seconds");
    handler.StartWithTimeout(5 * 1000);
    Thread.Sleep(2 * 1000);
    handler.StopOperationIfNotStartedYet();

    Thread.Sleep(4 * 1000);
    Console.WriteLine("Finished...");
    Console.ReadLine();
}

The result should be:

Starting with timeout of 5 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

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:

public class OperationHandler
{
    private IOperation _operation;
    private bool _stopCalled;

    public OperationHandler(IOperation operation)
    {
        _operation = operation;
    }

    public void StartWithTimeout(int timeoutMillis)
    {
        Task.Factory.StartNew(() =>
        {
            _stopCalled = false;
            Thread.Sleep(timeoutMillis);
            if (!_stopCalled)
                _operation.DoOperation();
        });
    }

    public void StopOperationIfNotStartedYet()
    {
        _stopCalled = true;
    }
}

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:

public class OperationHandler
{
    private IOperation _operation;
    private Timer _timer;

    public OperationHandler(IOperation operation)
    {
        _operation = operation;
    }

    public void StartWithTimeout(int timeoutMillis)
    {
        if (_timer != null)
            return;

        _timer = new Timer(
            state =>
            {
                _operation.DoOperation();
                DisposeOfTimer();
            }, null, timeoutMillis, timeoutMillis);
    }
        
    public void StopOperationIfNotStartedYet()
    {
        DisposeOfTimer();
    }

    private void DisposeOfTimer()
    {
        if (_timer == null)
            return;
        var temp = _timer;
        _timer = null;
        temp.Dispose();
    }

}

The result is:

Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

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:
public class OperationHandler
{
    private IOperation _operation;
    private ManualResetEvent _mre = new ManualResetEvent(false);

    public OperationHandler(IOperation operation)
    {
        _operation = operation;
    }

    public void StartWithTimeout(int timeoutMillis)
    {
        _mre.Reset();
        Task.Factory.StartNew(() =>
        {
            bool wasStopped = _mre.WaitOne(timeoutMillis);
            if (!wasStopped)
                _operation.DoOperation();
        });
    }
        
    public void StopOperationIfNotStartedYet()
    {
        _mre.Set();
    }
}

Result is:

Starting with timeout of 5 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...

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

public void StartWithTimeout(int timeoutMillis)
{
    if (_timer != null)
        return;
    ...

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:

class Program
{
    static void Main(string[] args)
    {
        var op = new MyOperation();
        var handler = new OperationHandler(op);

        Console.WriteLine("Starting with timeout of 10 seconds, 3 times");
        handler.StartWithTimeout(10 * 1000);
        Thread.Sleep(1000);
        handler.StartWithTimeout(10 * 1000);
        Thread.Sleep(1000);
        handler.StartWithTimeout(10 * 1000);

        Thread.Sleep(13 * 1000);

        Console.WriteLine("Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds");
        handler.StartWithTimeout(10 * 1000);
        Thread.Sleep(1000);
        handler.StartWithTimeout(10 * 1000);
        Thread.Sleep(1000);
        handler.StartWithTimeout(10 * 1000);

        Thread.Sleep(5 * 1000);
        handler.StopOperationIfNotStartedYet();

        Thread.Sleep(8 * 1000);
        Console.WriteLine("Finished...");
        Console.ReadLine();
    }
}

And here’s the solution using ManualResetEvent:

public class OperationHandler
{
    private IOperation _operation;
    private ManualResetEvent _mre = new ManualResetEvent(false);

    public OperationHandler(IOperation operation)
    {
        _operation = operation;
    }

    public void StartWithTimeout(int timeoutMillis)
    {
        Task.Factory.StartNew(() =>
        {
            bool wasStopped = _mre.WaitOne(timeoutMillis);
            if (!wasStopped)
                _operation.DoOperation();
        });
    }
        
    public void StopOperationIfNotStartedYet()
    {
        Task.Factory.StartNew(() =>
        {
            _mre.Set();
            Thread.Sleep(10);//This is necessary because if calling Reset() immediately, not all waiting threads will 'proceed'
            _mre.Reset();
        });
    }
}

The output, as expected is:

Starting with timeout of 10 seconds, 3 times
Operation started
Operation started
Operation started
Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds
Finished...

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:

public class OperationHandler
{
    private IOperation _operation;
    private AutoResetEvent _are = new AutoResetEvent(false);

    public OperationHandler(IOperation operation)
    {
        _operation = operation;
    }

    public void StartWithTimeout(int timeoutMillis)
    {
        _are.Reset();
        Task.Factory.StartNew(() =>
        {
            bool wasStopped = _are.WaitOne(timeoutMillis);
            if (!wasStopped)
                _operation.DoOperation();
        });
    }
        
    public void StopOperationIfNotStartedYet()
    {
        _are.Set();
    }
}

The result is:

Starting with timeout of 10 seconds, 3 times
Operation started
Operation started
Operation started
Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds
Operation started
Operation started
Finished...

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