Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / XML

Timeout Functions

Rate me:
Please Sign up or sign in to vote.
4.95/5 (29 votes)
11 Dec 2008CPOL8 min read 76.1K   929   57   6
This article explains about executing a function within a time limit. Also includes a helper class which helps to implement timeout functions easily. This article deals with running multiple timeout processes each with time limit.

timeout_functions/sample-program.png

Table of Contents

Introduction

Recently, I needed a smart way to execute functions within a specified time limit. If the function is unable to complete execution within that limit, it should stop processing and return back to the caller. Here, I am describing a method for writing timeout functions and the problems we may face. Here, we deal with a helper class that can be used to write timeout functions easily without having a deep knowledge of threading. Using this helper class, user can do the following:

  • Run a method within a timeout limit
  • Run multiple methods each with a timeout limit in a non-blocking fashion

How to Execute a Function Within a Timeout Limit?

Consider the following code:

Code listing: 1
C#
static void Main(string[] args) {
    DoLongRunningJob();
}

static void DoLongRunningJob() {
    for (int i = 0; i < 10000; i++) {
          // do something
    }
}

When a .NET application starts, it creates a thread which will be the main thread for that application. When this thread ends, the application will also end. In the above example, DoLongRunningJob is executed from the main thread and this operation blocks the main thread until DoLongRunningJob finishes. Since the main thread is blocked, we will not be able to stop this method when the timeout reaches.

The remedy to the above mentioned problem is running the DoLongRunningJob in a new thread and joining the main thread to the new thread using a timeout value. The System.Threading.Thread class provides a Join(TimeSpan) method which can accept a timeout value and blocks the calling thread until the thread finishes its operation or a timeout reaches. The following code shows DoLongRunningJob time out after 2 seconds.

Code listing: 2
C#
static void Main(string[] args) {
      Thread worker = new Thread(DoLongRunningJob);
      worker.Start();
      if (!worker.Join(TimeSpan.FromSeconds(2))) {
           worker.Abort();
           Console.WriteLine("Timedout!");
      }
}

static void DoLongRunningJob() {
      for (int i = 0; i < 10000; i++) {
           Console.WriteLine("Task {0}", i);
           Thread.Sleep(1000);
      }
}

We started DoLongRunningJob in a new thread and joined the main thread to it with a timeout of two seconds. Join will return to the caller when timeout reaches. But the worker thread will be executing still. To stop the worker thread, we use the Abort function. Output of the above code looks like:

timeout_functions/timedout-cmd.png

Putting Everything in a Re-usable Library

I have created a class called TimeoutProcess which helps to implement the timeout functions more easily. Here is the class diagram:

timeout_functions/CD.png

TimeoutProcess has a Start(object,TimeSpan,WaitCallback) method, which starts a new thread and invokes the WaitCallback delegate. It then calls Join on the newly created thread with the specified timeout value. When the specified timeout reaches, Join returns FALSE. Here is the Start method:

Code listing: 3
C#
public bool Start(object userState, TimeSpan timeOut, WaitCallback methodToExecute) {
     if (methodToExecute == null)
          throw new ArgumentNullException("methodToExecute");
     return Start(WorkerType.Normal, new DataCarrier(userState,
              methodToExecute, timeOut));
}

bool Start(WorkerType workerType, DataCarrier dataCarrier) {
     return worker[workerType].Start(dataCarrier);
}

worker is a Dictionary<WorkerType,ITimeoutWorker> which contains all supported worker types and its instances. ITimeoutWorker has a Start(DataCarrier) method which is implemented in the TimeoutWorker class.

Start(DataCarrier) method in TimeoutWorker class looks like:

Code listing: 4
C#
public override bool Start(DataCarrier dataCarrier) {
     // Starting the thread
     worker.Start(dataCarrier);
     // blocking current thread until worker finishes or timeout detected
     bool success = worker.Join(dataCarrier.TimeOut);
     if (!success)
          ProcessTimeout();
     return success;
}

void StartProcess(object userState) {
     DataCarrier data = userState as DataCarrier;
     WaitCallback methodToExecute = data.MethodToExecute;
     base.ExecuteMethod(userState, methodToExecute);
}

void ProcessTimeout() {
     worker.Abort();
}

TimeoutWorker is inherited from BaseWorker and ExecuteMethod in BaseWorker class looks like:

Code listing: 5
C#
void ExecuteMethod(object userState, WaitCallback methodToExecute) {
     methodToExecute(userState);
}

When the time limit reaches, ProcessTimeout calls Abort on the worker thread to stop it immediately. Calling Abort on a thread raises ThreadAbortException at the location where thread is currently executing and attempts to stop the thread. This stops the worker thread and returns the control back to the caller almost immediately. ThreadAbortException is a special kind of exception which will be re-thrown at the end of the catch block and it will not crash the application like other exceptions.

Usage of the TimeoutProcess class is very straightforward. See the following code:

Code listing: 6
C#
static void Main(string[] args) {
     TimeoutProcess process = new TimeoutProcess();
     bool success = process.Start("Any value here", TimeSpan.FromSeconds(3),
         DoLongRunningJob);
     if (!success)
          Console.WriteLine("Timedout!");
}

static void DoLongRunningJob(object userState) {
     for (int i = 0; i < 10000; i++) {
          Console.WriteLine("Task {0}", i);
          Thread.Sleep(1000);
     }
}

Here are two trivial test cases which ensures the process is timing out if the limit is short and is completing successfully when the timeout value is comparatively large.

Code listing: 7
C#
[Test]
public void CanTimeoutRunningProcess() {
     WaitCallback waitCallBack = GetLongRunningProcess();
     TimeoutProcess timeoutProcess = new TimeoutProcess();
     bool status = timeoutProcess.Start(null, TimeSpan.FromSeconds(2), waitCallBack);
     Assert.That(!status, "Process is not timing out");
}

[Test]
public void CanFinishRunningProcessWithMoreTimeoutLimit() {
     WaitCallback waitCallBack = GetAFastRunningProcess();
     TimeoutProcess timeoutProcess = new TimeoutProcess();
     bool status = timeoutProcess.Start(null, TimeSpan.FromSeconds(30), waitCallBack);
     Assert.That(status, "Process is not completing");
}

 WaitCallback GetLongRunningProcess() {
     return delegate(object obj)
     {
          for (int i = 0; i < 200000; i++) {
                 Debug.WriteLine(i);
          }
     };
 }

 WaitCallback GetAFastRunningProcess() {
     return delegate(object obj)
     {
          for (int i = 0; i < 200; i++) {
                 Debug.WriteLine(i);
          }
     };
 }

The Start method in the TimeoutProcess class blocks the calling thread for the specified time or until the process completes. Sometimes, we may have to run many processes simultaneously and each with a timeout limit. Since the Start method blocks, we will not be able to run multiple timeout processes simultaneously. To solve this, let us include some non-blocking methods to the TimeoutProcess class.

Note: WaitCallback is a delegate declared in System.Threading namespace.

Running Multiple Timeout Processes Simultaneously

In the TimeoutProcess class, a new method ( StartAsync(object,TimeSpan,WaitCallback)) is added to start a timeout process and will be returning to the caller immediately. The caller can subscribe to the AsyncProcessCompleted event which will be fired when the process is complete or timed out. The AsyncProcessEventArgs.HasProcessCompleted property can be used to determine the process completion status. This property returns FALSE if the function is timed out.

Here is the StartAsync(object,TimeSpan,WaitCallback) method which looks like:

Code listing: 8
C#
public void StartAsync(object userState, TimeSpan timeOut,
     WaitCallback methodToExecute) {
     if (methodToExecute == null)
          throw new ArgumentNullException("methodToExecute");
     // Starting the process
     Start(WorkerType.Asynchronous, new DataCarrier(userState, methodToExecute, timeOut));
}

Start calls the AsyncTimeoutWorker.Start method which looks like the following:

C#
 public override bool Start(DataCarrier dataCarrier) {
     this.IncrementAsyncProcessCount();
     return ThreadPool.QueueUserWorkItem(StartAsyncProcess, dataCarrier);
}

void StartAsyncProcess(object state) {
     DataCarrier data = state as DataCarrier;
     WaitCallback methodToExecute = data.MethodToExecute;
     Thread asyncWorker = new Thread(delegate(object obj)
     {
          base.ExecuteMethod(data.UserState, methodToExecute);
     });
     asyncWorker.IsBackground = true;
     asyncWorker.Start();
     bool success = asyncWorker.Join(data.TimeOut);
     this.DecrementAsyncProcessCount();
     AsyncProcessEventArgs args = new AsyncProcessEventArgs(data.UserState, success);
     if (!success)
          ProcessTimeout(asyncWorker, args, data.TimeOut);
     OnAsyncProcessCompleted(args);
}

You can see this method uses two threads, one for running the async thread and the other for running the process. The first thread, is a thread pool thread, which starts asyncWorker thread and calls Join on it. It then raises the AsyncProcessCompleted event to notify the process completion. The TimeoutProcess.AsyncProcessCount property can be used to get the number of async processes currently executing.

The following test illustrates the usage of async timeout process.

Code listing: 9
C#
EventWaitHandle[] waitHandles = null;
[Test]
public void MultipleAsyncProcessTimingout() {
      const int waitHandleCount = 10;
      waitHandles = new EventWaitHandle[waitHandleCount];
      TimeoutProcess timeoutProcess = new TimeoutProcess();
      timeoutProcess.AsyncProcessCompleted += timeoutProcess_MultipleAsyncProcessTimedout;
      for (int i = 0; i < waitHandleCount; i++) {
           waitHandles[i] = new AutoResetEvent(false);
           WaitCallback waitCallBack = GetLongRunningProcess();
           timeoutProcess.StartAsync(i, TimeSpan.FromSeconds(2), waitCallBack);
      }
      Assert.AreEqual(10, timeoutProcess.AsyncProcessCount,
          "Process count is not matching");
      Assert.That(timeoutProcess.IsAsyncProcessExecuting,
          "AsyncProcessExecuting flag is setting");
      WaitHandle.WaitAll(waitHandles);
}

void timeoutProcess_MultipleAsyncProcessTimedout(object sender, AsyncProcessEventArgs e) {
      int processIndex = (int)e.UserState;
      Assert.That(!e.HasProcessCompleted,
          string.Format("Async operation {0} not timed out", processIndex));
      waitHandles[processIndex].Set();
}

Problems and Workarounds

Waiting for More Time if the Worker is Still Active

When the timeout reaches, the worker thread will be aborted forcefully. If the method we are executing uses unmanaged code, the thread will be aborted when it reaches the next managed statement. This makes the worker thread to run for more than the time limit. When the timeout reaches, Abort is called on the worker thread and fires the TimeoutProcess.AsyncProcessCompleted event. Sometimes, the worker thread will not abort immediately and will be running in the abort requested state.

In such cases, an overload of TimeoutProcess.StartAsync allows you to specify the grace/excess time needed to wait for a thread to abort. This is possible by calling Join on the thread with a timeout value after calling Abort. Code listing 10 shows AsyncTimeoutWorker.ProcessTimeout method. Default grace period is 1 sec.

Code listing: 10
C#
void ProcessTimeout(Thread thread, AsyncProcessEventArgs args, TimeSpan abortWait) {
     thread.Abort();
     // waiting for specified time to abort. Default is 1 second
     if (!thread.Join(abortWait)) {
            // thread won't end
            args.IsThreadRunning = true;
            args.AsyncWorker = thread;
     }
}

AsyncProcessEventArgs.IsThreadRunning indicates that the thread is still running. You can get an instance to the worker using AsyncProcessEventArgs.AsyncWorker and wait on it again if you need. Code listing 11 shows how to do this.

Cleaning Up the Resources Used

If you look at the ProcessTimeout method listed in Code listing 10, you can see that it calls Abort on the specified thread. This will request the thread to abort immediately by throwing an exception. If your code is executing abort unsafe methods, chances are they're for uncleaned resources such as file handles.

Calling Abort on a thread is safe, when the current location of the thread is known. When Abort is called from a different thread, it is tough to identify the exact location. Since the calling of Abort function is unpredictable, it is always good to keep your cleanup logic in finally blocks. In most cases, the finally block is executed when a thread aborts. It is recommended not to write blocking code in the finally block as it may affect the time to abort.

The other way to cleanup resources is by running the timeout method in a new application domain. If the thread is not aborting after the timeout period, you can unload the application domain manually after calling abort. Unloading an application domain will stop all active threads and release the resources used by them. If the thread is still unable to abort while unloading an application domain, an exception of type CannotUnloadAppDomainException [^] will be thrown.

Here is a sample program which executes the method on a new application domain and unloads it if the thread does not stop after the specified time.

Code listing: 11
C#
class Program
{
     static AppDomain newDomain = null;
     static void Main(string[] args) {
          Console.WriteLine("Starting process");
          TimeoutProcess process = new TimeoutProcess();
          process.AsyncProcessCompleted += process_AsyncProcessCompleted;
          newDomain = AppDomain.CreateDomain("domain");
          process.StartAsync(null, TimeSpan.FromSeconds(1), delegate(object obj)
          {
               newDomain.DoCallBack(DoLongRunningJob);
          });
}

static void process_AsyncProcessCompleted(object sender, AsyncProcessEventArgs e) {
     if (e.IsThreadRunning) {
          // Process timedout, but worker thread still executing
          // Giving some more time for abort
          e.AsyncWorker.Abort();
          if (!e.AsyncWorker.Join(1000)) {
               // will not end. unload domain
               AppDomain.Unload(newDomain);
               Console.WriteLine("Domain unloaded");
          }
     }
}

static void DoLongRunningJob() {
     for (int i = 0; i < 10000; i++) {
          try {
               Console.WriteLine("Task {0}", i);
               Thread.Sleep(1000);
          }
          catch (ThreadAbortException) {
               Thread.ResetAbort();
          }
     }
}

Calling Abort from Another Thread is a Bad Practice?

The methods described in this article call Abort on a thread from another thread, which can be considered as a bad practice. Aborting a thread using any safe abort techniques, calling abort from the same thread or using a boolean variable to signal abort, is always a good practice. However, the disadvantage of relying on such kind of methods is that, if the thread is blocked, there is no way to check the abort condition. Consider the following code:

Code listing: 12
C#
while(!timedOut){
     ExecuteBlockingCode();
}

In the above code, the while(!timedOut) will not be executed for all the time as ExecuteBlockingCode() blocks this thread. If the thread is blocked due to executing managed statements, Thread.Interrupt can be used which will throw ThreadInterruptedException [^] and continues execution until it blocks next time. This will check the condition (while(!timedOut)). But if thread is blocked because of unmanaged code, interrupt won't work. This is the reason for using abort to stop the thread forcefully.

Conclusion

Running functions with a timeout limit is helpful when you are not sure about the time required to execute the function. The TimeoutProcess class discussed in this article provides an easy method to run functions within a time limit. This class executes the supplied method in a new thread, so if your method is updating UI, you should take precautions to avoid cross thread communication errors.

When non-blocking timeout methods are executing, there will be resource overhead as it uses two threads to execute the method. Please suggest a better approach if you have any.

That's all with this article. I hope you find this article helpful. I'd appreciate if you rate and leave your feedback below.

Thank you for reading, happy coding!

History

  • 10th December, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer ThoughtWorks
India India
Call me Navaneeth Some years ago--never mind how long precisely, I was doing programs with C and C++. When .NET came, I started with C#,ASP.NET and SQL Server.

Comments and Discussions

 
QuestionSource code Pin
kiquenet.com28-May-13 23:42
professionalkiquenet.com28-May-13 23:42 
Generalits good - have 5 Pin
Pranay Rana2-Jan-11 19:01
professionalPranay Rana2-Jan-11 19:01 
GeneralExcellent Pin
George Katsikas23-Mar-09 0:45
George Katsikas23-Mar-09 0:45 
GeneralRe: Excellent Pin
N a v a n e e t h23-Mar-09 15:07
N a v a n e e t h23-Mar-09 15:07 
GeneralPerfect Pin
Abhijit Jana17-Dec-08 2:45
professionalAbhijit Jana17-Dec-08 2:45 
GeneralRe: Perfect Pin
N a v a n e e t h19-Dec-08 19:53
N a v a n e e t h19-Dec-08 19:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.