0

I was looking for a Task based way to detect a timeout for a task, without having to Wait() for it.

The timeout should be put on the innermost task in a chain of continuations, while the Exception should get caught only at the outermost level.

My solution, instead of blocking the execution, returns a Task which wraps the original one, allowing the user to catch an exception in case of time out.

So I came up with this code:

public static Task<T> Timeout<T>(Task<T> baseTask,  int milliseconds)
{
    var timer = Delay(milliseconds);

    return Task.Factory.ContinueWhenAny(
        new []
        {
            baseTask,
            timer.ContinueWith<T>(x => { throw new TaskTimeOutException(); })
        },
        x => x.Result
    );
}

The function Delay() is described in the accepted solution at How to put a task to sleep (or delay) in C# 4.0? .

I would like to improve my code, basically I have a few questions:

  • Can someone see possible pitfalls?
  • Should the timeout cause a cancellation of the original Task?

Thank you.

EDIT

Based on comments I developed this slight improvement:

public static Task<T> Timeout<T>(Task<T> baseTask,  int milliseconds)
{
    var timer = Delay(milliseconds);

    return Task.Factory.ContinueWhenAny(
        new []
        {
            baseTask,
            timer
        },
        x =>
        {
            if (x.Equals(baseTask)) return baseTask.Result;
            throw new TaskTimeOutException();
        }
    );
}
Community
  • 1
  • 1
Andrea Rossini
  • 320
  • 2
  • 11
  • there is one little issue: if timeoutexception will be thrown there is "no catch" block that catch it beacause that exception will be thronw in one detached thread. I've found this problem and i've resolved this problem managing the unandletapplication exception event. – Othello .net dev Apr 19 '16 at 07:34
  • You mean that if the original tasks succedes the timer would still fire the exception, making it impossible to catch? Because I think that, if the original task fails to complete in time, the exception can be catched by user code, as the result of the composite task. – Andrea Rossini Apr 19 '16 at 08:07
  • I've wrote a simple test program, see it below – Othello .net dev Apr 19 '16 at 12:30

2 Answers2

1

You can try to create task with CancellationToken and then call tokenSource.CancelAfter(...) like this

var tokeSource = new CancellationTokenSource();
Task.Run(() => { Console.WriteLine("processing"); }, tokenSource.Token);
tokenSource.CancelAfter(TimeSpan.FromSeconds(30));

In .Net 4.0 you can implement CancelAfter by yourself with something like

public static class CancellationTokenSourceExtensions
{
    public static Task CancelAfter(this CancellationTokenSource cts, TimeSpan timeout)
    {
        return Task.Delay(timeout).ContinueWith(t => cts.Cancel());
    }
}

I personally think that cts-based solution is more with the spirit of TPL.

Maxim Kosov
  • 1,930
  • 10
  • 19
0
class Program
    {
        static bool continueRun = true;
        static void Main(string[] args)
        {
            System.AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionTrapper;

            try
            {
                Console.WriteLine("Enter code");
                Task.Run(() =>
                {
                    Console.WriteLine("Enter task");
                    System.Threading.Thread.Sleep(1000);
                    Console.WriteLine("End thread sleep");
                    throw new Exception("Inner task execution");
                });
                Console.WriteLine("Exit code");
            }
            catch (Exception err)
            {
                Console.WriteLine("Exception code");
            }
            finally
            {
                Console.WriteLine("Finally code");
            }


            Console.WriteLine("Press a key to exit");
            Console.ReadLine();
        }

        private static void UnhandledExceptionTrapper(object sender, UnhandledExceptionEventArgs e)
        {
            Console.Write("Unhandled exception");
            continueRun = false;
            Console.ReadLine();
        }
    }

Ouput:

Enter code
Exit  code
Finally code
Press a key to exit
Enter Task
End thread sleep

in visual studio you'll see raising of one uncatched exception

  • The runtime treats your Exception as unhandled because your example actually fire-and-forgets the Task. I **always** wait my tasks in some top level, so I can catch Exceptions in well-defined points. – Andrea Rossini Apr 19 '16 at 12:40
  • 1
    My bad, I should rename the question. What I wanted was to put the timeout at the innermost task, without waiting it, so the exception could propagate to the first block of code waiting for it. – Andrea Rossini Apr 20 '16 at 15:05