简体   繁体   中英

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!

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();
}

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.

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

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();
} 

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

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();
        }
    }
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();
}   

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM