Timeout a function call in C#


There are some scenarios when you can’t afford to allow any long running external/internal method to take forever and eat up your processing resource. You should time it out if it takes longer than X units.

 There are several ways to do that. I like the way using cancellation token the most.

 Here we are calling GetResult() method which might take longer than 5000ms.Code snippet:

 int QueryTimeOut = 5000; //in ms
var tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
var task = Task.Factory.StartNew(() => queryRun.GetResult(), token);
timedOut = false;

if (!task.Wait(QueryTimeOut, token))
{
timedOut = true;
message = String.Format(" Method Timed out");
tokenSource.Cancel();
return -1;
}
Int32 result = Convert.ToInt32(task.Result);
task.Dispose();
return result;

NOTE: Above code does not kill the GetResult() which still runs in background. To Abort processing of GetResult/ long running method, please see below snippet:

 using System;
using System.Threading;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace n
{
public class MonitorSample
{

private static int GetResult()
{
int result = 0;
while (++result < 500)
{
Console.WriteLine(result);
Thread.Sleep(5);
}
return result;
}
public static void Main(String[] args)
{
int QueryTimeOut = 50; //in ms
int result = 0;
Boolean timeOut = false;
ManualResetEvent wait = new ManualResetEvent(false);
Thread work = new Thread(new ThreadStart(() =>
{
//some long running method requiring synchronization
result = GetResult();
wait.Set();
}));
work.Start();
Boolean signal = wait.WaitOne(QueryTimeOut);
if (!signal) {
work.Abort();
timeOut = true;
}
Console.WriteLine(String.Format("Result = {0}, timedout = {1}", result, timeOut));
Console.ReadKey();
}
}
}
Comments (7)

  1. Phil Bolduc says:

    If the operation gets a time out while wating and you enter the "if" block, shoudn't you cancel the running task by calling token.Cancel() ?  Otherwise, your queryRun could continue to execute in the background.

  2. Nikhil_Agarwal says:

    Yes Phil, I should include that inside task.Wait() if block.

  3. Veldhuizen, Marcel says:

    Actually, I don't think this code does what you think it does.

    Unless you pass the cancellation token to the queryRun.GetResult() method and check for cancellation, the actual execution of the method will not be cancelled at all. Your application will stop waiting for the result, but the actual work will still be going. In other words, it will still be taking up those processing resources you are referring to.

    To actually stop executing any method that does not implement some kind of timeout mechanism, you have to quite unceremoniously abort the thread it's executing on.

  4. Nikhil_Agarwal says:

    #Marcel, I have verified this code, tokenSource.Cancel() is responsible to cancel the task once the timeout value is reached. After this the background task is cancelled and will not be completed. Can you please run the following code, change the QueryTimeout value and observe the behavior of the program.

    using System;

    using System.Threading;

    using System.Collections.Generic;

    using System.IO;

    using System.Threading.Tasks;

    namespace n{

       public class MonitorSample

       {

           private static Int32 GetResult()

           {

               Thread.Sleep(5);

               return 3;

           }

           public static void Main(String[] args)

           {

               int QueryTimeOut = 50; //in ms

               var tokenSource = new CancellationTokenSource();

               CancellationToken token = tokenSource.Token;

               Int32 result = 0;

               Boolean timeout = false;

               var task = Task.Factory.StartNew(() => GetResult(), token);

               if (!task.Wait(QueryTimeOut))

               {

                   timeout = true;

                   tokenSource.Cancel();

               }

               else

               {

                   result = Convert.ToInt32(task.Result);

                   task.Dispose();

               }

               Console.WriteLine(String.Format("Result = {0}, timedout = {1}",result, timeout));

               Console.ReadKey();

           }

       }

    }

    Let me know if you have further comments.

  5. RichardDeeming says:

    A slight change to your GetResult method will illustrate the problem:

    private static int GetResult()

    {

       int result = 0;

       while (++result < 500)

       {

           Console.WriteLine(result);

           Thread.Sleep(5);

       }

       return result;

    }

    When you see the "timedout" message, wait for a few seconds; you'll see the messages from the GetResult method are still being written to the console. The task has been cancelled, but the method is still running.

  6. Nikhil_Agarwal says:

    My GetResult() is an external method. I cannot pass token to the method. Do we have alternatives to stop the processing?

  7. Thanks Richard and Marcel for enlightening me.  I tried following code and works well. This is using thread and without cancellation token.

    using System;

    using System.Threading;

    using System.Collections.Generic;

    using System.IO;

    using System.Threading.Tasks;

    namespace n

    {

       public class MonitorSample

       {

           private static int GetResult()

           {

               int result = 0;

               while (++result < 500)

               {

                   Console.WriteLine(result);

                   Thread.Sleep(5);

               }

               return result;

           }

           public static void Main(String[] args)

           {

               int QueryTimeOut = 50; //in ms

               int result = 0;

               Boolean timeOut = false;

               ManualResetEvent wait = new ManualResetEvent(false);

               Thread work = new Thread(new ThreadStart(() =>

               {

                   //some long running method requiring synchronization

                   result = GetResult();

                   wait.Set();

               }));

               work.Start();

               wait.WaitOne(QueryTimeOut);

               if (work.IsAlive) {

                   work.Abort();

                   timeOut = true;

               }

               Console.WriteLine(String.Format("Result = {0}, timedout = {1}", result, timeOut));

               Console.ReadKey();

           }

       }

    }