Monitoring a synchronous method for timeout

后端 未结 4 1281
孤街浪徒
孤街浪徒 2020-12-30 04:20

I\'m looking for an efficient way to throw a timeout exception if a synchronous method takes too long to execute. I\'ve seen some samples but nothing that quite does what I

相关标签:
4条回答
  • 2020-12-30 04:25

    To elabolate on Timothy Shields clean solution:

            if (task == await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(3))))
            {
                return await task;
            }
            else
                throw new TimeoutException();
    

    This solution, I found, will also handle the case where the Task has a return value - i.e:

    async Task<T>
    

    More to be found here: MSDN: Crafting a Task.TimeoutAfter Method

    0 讨论(0)
  • 2020-12-30 04:36

    I have re-written this solution for .NET 4.0 where some methods are not available e.g.Delay. This version is monitoring a method which returns object. How to implement Delay in .NET 4.0 comes from here: How to put a task to sleep (or delay) in C# 4.0?

    public class OperationWithTimeout
    {
        public Task<object> Execute(Func<CancellationToken, object> operation, TimeSpan timeout)
        {
            var cancellationToken = new CancellationTokenSource();
    
            // Two tasks are created. 
            // One which starts the requested operation and second which starts Timer. 
            // Timer is set to AutoReset = false so it runs only once after given 'delayTime'. 
            // When this 'delayTime' has elapsed then TaskCompletionSource.TrySetResult() method is executed. 
            // This method attempts to transition the 'delayTask' into the RanToCompletion state.
            Task<object> operationTask = Task<object>.Factory.StartNew(() => operation(cancellationToken.Token), cancellationToken.Token);
            Task delayTask = Delay(timeout.TotalMilliseconds);
    
            // Then WaitAny() waits for any of the provided task objects to complete execution.
            Task[] tasks = new Task[]{operationTask, delayTask};
            Task.WaitAny(tasks);
    
            try
            {
                if (!operationTask.IsCompleted)
                {
                    // If operation task didn't finish within given timeout call Cancel() on token and throw 'TimeoutException' exception.
                    // If Cancel() was called then in the operation itself the property 'IsCancellationRequested' will be equal to 'true'.
                    cancellationToken.Cancel();
                    throw new TimeoutException("Timeout waiting for method after " + timeout + ". Method was to slow :-)");
                }
            }
            finally
            {
                cancellationToken.Dispose();
            }
    
            return operationTask;
        }
    
        public static Task Delay(double delayTime)
        {
            var completionSource = new TaskCompletionSource<bool>();
            Timer timer = new Timer();
            timer.Elapsed += (obj, args) => completionSource.TrySetResult(true);
            timer.Interval = delayTime;
            timer.AutoReset = false;
            timer.Start();
            return completionSource.Task;
        }
    }
    

    How to use it then in Console app.

        public static void Main(string[] args)
        {
            var operationWithTimeout = new OperationWithTimeout();
            TimeSpan timeout = TimeSpan.FromMilliseconds(10000);
    
            Func<CancellationToken, object> operation = token =>
            {
                Thread.Sleep(9000); // 12000
    
                if (token.IsCancellationRequested)
                {
                    Console.Write("Operation was cancelled.");
                    return null;
                }
    
                return 123456;
            };
    
            try
            {
                var t = operationWithTimeout.Execute(operation, timeout);
                var result = t.Result;
                Console.WriteLine("Operation returned '" + result + "'");
            }
            catch (TimeoutException tex)
            {
                Console.WriteLine(tex.Message);
            }
    
            Console.WriteLine("Press enter to exit");
            Console.ReadLine();
        }
    
    0 讨论(0)
  • 2020-12-30 04:38

    Jasper's answer got me most of the way, but I specifically wanted a void function to call a non-task synchronous method with a timeout. Here's what I ended up with:

    public static void RunWithTimeout(Action action, TimeSpan timeout)
    {
        var task = Task.Run(action);
        try
        {
            var success = task.Wait(timeout);
            if (!success)
            {
                throw new TimeoutException();
            }
        }
        catch (AggregateException ex)
        {
            throw ex.InnerException;
        }
    }
    

    Call it like:

    RunWithTimeout(() => File.Copy(..), TimeSpan.FromSeconds(3));
    
    0 讨论(0)
  • 2020-12-30 04:44

    If you have a Task called task, you can do this:

    var delay = Task.Delay(TimeSpan.FromSeconds(3));
    var timeoutTask = Task.WhenAny(task, delay);
    

    If timeoutTask.Result ends up being task, then it didn't timeout. Otherwise, it's delay and it did timeout.

    I don't know if this is going to behave identically to what you have implemented, but it's the built-in way to do this.

    0 讨论(0)
提交回复
热议问题