
Table of Contents
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
static void Main(string[] args) {
DoLongRunningJob();
}
static void DoLongRunningJob() {
for (int i = 0; i < 10000; i++) {
}
}
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
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:

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:

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
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
public override bool Start(DataCarrier dataCarrier) {
worker.Start(dataCarrier);
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
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
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
[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
public void StartAsync(object userState, TimeSpan timeOut,
WaitCallback methodToExecute) {
if (methodToExecute == null)
throw new ArgumentNullException("methodToExecute");
Start(WorkerType.Asynchronous, new DataCarrier(userState, methodToExecute, timeOut));
}
Start
calls the AsyncTimeoutWorker.Start
method which looks like the following:
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
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
void ProcessTimeout(Thread thread, AsyncProcessEventArgs args, TimeSpan abortWait) {
thread.Abort();
if (!thread.Join(abortWait)) {
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
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) {
e.AsyncWorker.Abort();
if (!e.AsyncWorker.Join(1000)) {
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
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