Karan
Karan

Reputation: 15104

Implementing a timeout in c#

I am new to c#; I have mainly done Java.

I want to implement a timeout something along the lines:

int now= Time.now();
while(true)
{
  tryMethod();
  if(now > now+5000) throw new TimeoutException();
}

How can I implement this in C#? Thanks!

Upvotes: 20

Views: 121517

Answers (8)

Lakedaimon
Lakedaimon

Reputation: 1944

Using mature library Polly it can be implemented using optimistic (thus CancellationToken based) as follows:


AsyncTimeoutPolicy policy = Policy.TimeoutAsync(60, TimeoutStrategy.Optimistic);
await policy.ExecuteAsync(async cancel => await myTask(cancel), CancellationToken.None);

myTask(cancel) should be of signature Func<CancellationToken, Task> e.g. async Task MyTast(CancellationToken token) {...}

Upvotes: 0

Vikas Bhola
Vikas Bhola

Reputation: 1

CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(10000);
try
{
    Task task = Task.Run(() => { methodToTimeoutAfter10Seconds(); }, cts.Token);
    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
    using (cts.Token.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
    {
        if (task != await Task.WhenAny(task, tcs.Task))
        {
            throw new OperationCanceledException(cts.Token);
        }
    }
    /* Wait until the task is finish or timeout. */
    task.Wait();

    /* Rest of the code goes here */
    
}
catch (TaskCanceledException)
{
    Console.WriteLine("Timeout");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Timeout");
}
catch (Exception ex)
{
    Console.WriteLine("Other exceptions");
}
finally
{
    cts.Dispose();
}   

Upvotes: 0

Yan Brunet
Yan Brunet

Reputation: 4897

The question is quite old, but yet another option.

using(CancellationTokenSource cts = new CancellationTokenSource(5000))
{
  cts.Token.Register(() => { throw new TimeoutException(); });
  while(!cts.IsCancellationRequested)
  {
    tryMethod();
  }
}

Technically, you should also propagate the CancellationToken in the tryMethod() to interupt it gracefully.

Working demo: (note I had to remove the exception throwing behavior as .netfiddle doesn't like it.)

https://dotnetfiddle.net/WjRxyk

Upvotes: 14

Scott Chamberlain
Scott Chamberlain

Reputation: 127603

One possible way would be:

Stopwatch sw = new Stopwatch();
sw.Start();

while(true)
{
    tryMethod();
    if(sw.ElapsedMilliseconds > 5000) throw new TimeoutException();
}

However you currently have no way to break out of your loop. I would recommend having tryMethod return a bool and change it to:

Stopwatch sw = new Stopwatch();
sw.Start();

while(!tryMethod())
{
    if(sw.ElapsedMilliseconds > 5000) throw new TimeoutException();
}

Upvotes: 46

fmaccaroni
fmaccaroni

Reputation: 3916

Using Tasks for custom timeout on Async method

Here my implementation of a custom class with a method to wrap a task to have a timeout.

public class TaskWithTimeoutWrapper
{
    protected volatile bool taskFinished = false;

    public async Task<T> RunWithCustomTimeoutAsync<T>(int millisecondsToTimeout, Func<Task<T>> taskFunc, CancellationTokenSource cancellationTokenSource = null)
    {
        this.taskFinished = false;

        var results = await Task.WhenAll<T>(new List<Task<T>>
        {
            this.RunTaskFuncWrappedAsync<T>(taskFunc),
            this.DelayToTimeoutAsync<T>(millisecondsToTimeout, cancellationTokenSource)
        });

        return results[0];
    }

    public async Task RunWithCustomTimeoutAsync(int millisecondsToTimeout, Func<Task> taskFunc, CancellationTokenSource cancellationTokenSource = null)
    {
        this.taskFinished = false;

        await Task.WhenAll(new List<Task>
        {
            this.RunTaskFuncWrappedAsync(taskFunc),
            this.DelayToTimeoutAsync(millisecondsToTimeout, cancellationTokenSource)
        });
    }

    protected async Task DelayToTimeoutAsync(int millisecondsToTimeout, CancellationTokenSource cancellationTokenSource)
    {
        await Task.Delay(millisecondsToTimeout);

        this.ActionOnTimeout(cancellationTokenSource);
    }

    protected async Task<T> DelayToTimeoutAsync<T>(int millisecondsToTimeout, CancellationTokenSource cancellationTokenSource)
    {
        await this.DelayToTimeoutAsync(millisecondsToTimeout, cancellationTokenSource);

        return default(T);
    }

    protected virtual void ActionOnTimeout(CancellationTokenSource cancellationTokenSource)
    {
        if (!this.taskFinished)
        {
            cancellationTokenSource?.Cancel();
            throw new NoInternetException();
        }
    }

    protected async Task RunTaskFuncWrappedAsync(Func<Task> taskFunc)
    {
        await taskFunc.Invoke();

        this.taskFinished = true;
    }

    protected async Task<T> RunTaskFuncWrappedAsync<T>(Func<Task<T>> taskFunc)
    {
        var result = await taskFunc.Invoke();

        this.taskFinished = true;

        return result;
    }
}

Then you can call it like this:

await new TaskWithTimeoutWrapper().RunWithCustomTimeoutAsync(10000, () => this.MyTask());

or

var myResult = await new TaskWithTimeoutWrapper().RunWithCustomTimeoutAsync(10000, () => this.MyTaskThatReturnsMyResult());

And you can add a cancellation token if you want to cancel the running async task if it gets to timeout.

Hope it helps

Upvotes: 4

JamieSee
JamieSee

Reputation: 13030

As long as tryMethod() doesn't block this should do what you want:

Not safe for daylight savings time or changing time zones when mobile:

DateTime startTime = DateTime.Now;

while(true)
{
    tryMethod();
    if(DateTime.Now.Subtract(startTime).TotalMilliseconds > 5000)
        throw new TimeoutException();
}

Timezone and daylight savings time safe versions:

DateTime startTime = DateTime.UtcNow;

while(true)
{
    tryMethod();
    if(DateTime.UtcNow.Subtract(startTime).TotalMilliseconds > 5000)
        throw new TimeoutException();
} 

(.NET 3.5 or higher required for DateTimeOffset.)

DateTimeOffset startTime = DateTimeOffset.Now;

while(true)
{
    tryMethod();
    if(DateTimeOffset.Now.Subtract(startTime).TotalMilliseconds > 5000)
        throw new TimeoutException();
} 

Upvotes: 4

Samuel Poirier
Samuel Poirier

Reputation: 1250

Another way I like to do it:

public class TimeoutAction
    {
        private Thread ActionThread { get; set; }
        private Thread TimeoutThread { get; set; }
        private AutoResetEvent ThreadSynchronizer { get; set; }
        private bool _success;
        private bool _timout;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="waitLimit">in ms</param>
        /// <param name="action">delegate action</param>
        public TimeoutAction(int waitLimit, Action action)
        {
            ThreadSynchronizer = new AutoResetEvent(false);
            ActionThread = new Thread(new ThreadStart(delegate
            {
                action.Invoke();
                if (_timout) return;
                _timout = true;
                _success = true;
                ThreadSynchronizer.Set();
            }));

            TimeoutThread = new Thread(new ThreadStart(delegate
            {
                Thread.Sleep(waitLimit);
                if (_success) return;
                _timout = true;
                _success = false;
                ThreadSynchronizer.Set();
            }));
        }

        /// <summary>
        /// If the action takes longer than the wait limit, this will throw a TimeoutException
        /// </summary>
        public void Start()
        {
            ActionThread.Start();
            TimeoutThread.Start();

            ThreadSynchronizer.WaitOne();

            if (!_success)
            {
                throw new TimeoutException();
            }
            ThreadSynchronizer.Close();
        }
    }

Upvotes: 1

JMK
JMK

Reputation: 28069

I think you could do this with a timer and a delegate, my example code is below:

using System;
using System.Timers;

class Program
{
    public delegate void tm();

    static void Main(string[] args)
    {
        var t = new tm(tryMethod);
        var timer = new Timer();
        timer.Interval = 5000;

        timer.Start();

        timer.Elapsed += (sender, e) => timer_Elapsed(t);
        t.BeginInvoke(null, null);
    }

    static void timer_Elapsed(tm p)
    {
        p.EndInvoke(null);
        throw new TimeoutException();
    }

    static void tryMethod()
    {
        Console.WriteLine("FooBar");
    }
}

You have tryMethod, you then create a delegate and point this delegate at tryMethod, then you start this delegate Asynchronously. Then you have a timer, with the Interval being 5000ms, you pass your delegate into your timer elapsed method (which should work as a delegate is a reference type, not an value type) and once the 5000 seconds has elapsed, you call the EndInvoke method on your delegate.

Upvotes: 6

Related Questions