20

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!

Karan
  • 14,824
  • 24
  • 91
  • 157
  • You'll want to look at the [Stopwatch](http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx) class. [Here](http://stackoverflow.com/a/5019172/95573) is an example – SwDevMan81 Apr 13 '12 at 15:43
  • 2
    What sort of thing does `tryMethod` do? In the code above, your if statement will only be reached after `trymethod` has completed. – JMK Apr 13 '12 at 15:43
  • 2
    This seems like something you might want to execute asynchronously? – emd Apr 13 '12 at 15:44
  • Does tryMethod() sync or async? – sll Apr 13 '12 at 15:45
  • You can use the `Timer` class to run some code in 5 seconds. Obviously you'll need to abort whatever other thread or something is calling tryMethod over and over; just throwing an exception in the `timer.Tick` event wouldn't do what you need . – Servy Apr 13 '12 at 15:49
  • I assume tryMethod is a quick check for something – Karan Apr 13 '12 at 16:12

8 Answers8

44

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();
}
Corio
  • 395
  • 7
  • 20
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 2
    Has issues when it comes to daylight savings days – Servy Apr 13 '12 at 15:47
  • 2
    @Servy you are correct, Edited my answer to use stopwatch instead of DateTime. – Scott Chamberlain Apr 13 '12 at 15:49
  • I'm fairly sure that OP wants to be able to perform the timeout operation on tryMethod though – KingCronus Apr 13 '12 at 15:49
  • 1
    @AdamKing I do not agree as that is not what is original java code is doing, it is just repeatedly calling tryMethod() for 5000 ms. – Scott Chamberlain Apr 13 '12 at 15:50
  • 1
    @AdamKing Then why is it in a loop, and not just once? – Servy Apr 13 '12 at 15:50
  • My bad. I had assumed it was an async operation. – KingCronus Apr 13 '12 at 15:52
  • is there a StopWatch library I need to include to my references? Visual studio highlights it and suggests me to generate a new class for StopWatch ... – Karan Apr 13 '12 at 16:18
  • used the following for those who are interested: System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); – Karan Apr 13 '12 at 16:19
  • @Newton put a `using System.Diagnostics;` at the top of your code. it is equivalent to `import System.Diagnostics.*;` in Java. – Scott Chamberlain Apr 13 '12 at 16:27
  • ack. However, I was surprised that visual studio did not pick that up. It normally does - as in it normally highlights it with the autocorrect feature and suggest you to import it with "using" – Karan Apr 13 '12 at 16:30
  • Why are you using Stopwatch? what if there are no clocks and tiimers allowed and the only way to implement timeout is using the past and the current datetime value – Simple Fellow Jan 23 '17 at 13:06
  • @SimpleFellow I use a stopwatch because a stopwatch is the best way to measure a duration, the OP wants to try the operation over and over again for 5000 milliseconds as fast as he can. Once the 5000 milliseconds are over and the operation has not succeeded then a exception is thrown. – Scott Chamberlain Sep 12 '17 at 21:52
13

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

Yan Brunet
  • 4,727
  • 2
  • 25
  • 35
6

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.

JMK
  • 27,273
  • 52
  • 163
  • 280
  • I think it opposite of what OP wants - this code ensures that method does not finish for at least 5000ms, it looks like OP want to run a method repeatedly for at most 5000ms. – Alexei Levenkov Apr 13 '12 at 16:10
  • 1
    Ah apologies, I got this backwards! – JMK Apr 13 '12 at 16:13
  • The other answer (the while loop version) does not seem to work with blocking operation. I will do some testing, but just asking for now, will this timer approach work for blocking? – liang Jul 07 '14 at 10:57
  • 1
    @liang You're asking me about something I did two years ago! I'm not 100% sure, but I think that some of the timers are synchronous while others are asynchronous, you need to use the correct one. – JMK Jul 07 '14 at 12:41
4

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();
} 
JamieSee
  • 12,696
  • 2
  • 31
  • 47
  • is it better to convert the DateTime to Universal time in order to take different time zones into account? Also if I use UtcNow would it make any difference? – Simple Fellow Jan 23 '17 at 13:34
  • @SimpleFellow There are two ways to make this timezone and daylight savings time safe. See the updated answer. Using UtcNow is better than converting because the conversion could still have problems based on when the conversion is performed. – JamieSee Feb 07 '17 at 18:33
4

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

fmaccaroni
  • 3,846
  • 1
  • 20
  • 35
1

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();
        }
    }
mosimo
  • 3
  • 2
Samuel Poirier
  • 1,240
  • 2
  • 15
  • 30
0
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();
}   
0

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) {...}

Lakedaimon
  • 1,784
  • 2
  • 11
  • 10