I want to wait for a Task<T> to complete with some special rules:
If it hasn't completed after X milliseconds, I want to display a message to the user.
And if it hasn't completed after Y milliseconds, I want to automatically request cancellation.

I can use Task.ContinueWith to asynchronously wait for the task to complete (i.e. schedule an action to be executed when the task is complete), but that doesn't allow to specify a timeout.
I can use Task.Wait to synchronously wait for the task to complete with a timeout, but that blocks my thread.
How can I asynchronously wait for the task to complete with a timeout?

You are right. I am surprised it does not provide for timeout. Maybe in .NET 5.0... Of course we can build the timeout into the task itself but that is no good, such things must come free.
–
AliostadNov 21 '10 at 14:46

3

While it would still require logic for the two-tier timeout you describe, .NET 4.5 does indeed offer a simple method for creating a timeout-based CancellationTokenSource. Two overloads to the constructor are available, one taking a integer millisecond delay and one taking a TimeSpan delay.
–
patridgeJul 31 '12 at 18:24

Addition: at the request of a comment on my answer, here is an expanded solution that includes cancellation handling. Note that passing cancellation to the task and the timer means that there are multiple ways cancellation can be experienced in your code, and you should be sure to test for and be confident you properly handle all of them. Don't leave to chance various combinations and hope your computer does the right thing at runtime.

It should be mentioned that even though Task.Delay can complete before long running task, allowing you to handle a timeout scenario, this does NOT cancel the long running task itself; WhenAny simply lets you know that one of the tasks passed to it has completed. You will have to implement a CancellationToken and cancel the long running task yourself.
–
Jeff SchumacherAug 23 '13 at 15:40

5

It may also be noted that the Task.Delay task is backed by a system timer which will continue to be tracked until the timeout expires regardless of how long SomeOperationAsync takes. So if this overall code snippet executes a lot in a tight loop, you're consuming system resources for timers until they all timeout. The way to fix that would be to have a CancellationToken that you pass to Task.Delay(timeout, cancellationToken) that you cancel when SomeOperationAsync completes to release the timer resource.
–
Andrew ArnottJan 17 '14 at 13:49

@JeffSchumacher It would be amazingly useful if somebody could demonstrate an example of implementing a CancellationToken using the structure in the answer.
–
Chris PickfordAug 7 '14 at 15:13

You could create two additional tasks (that complete after the specified timeouts) and then use WaitAny to wait for whichever completes first. If the task that completed first is your "work" task, then you're don. If the task that completed first is a timeout task, then you can react to the timeout (e.g. request cancellation).

I've seen this technique used by an MVP I really respect, it seems much cleaner to me than the accepted answer. Perhaps an example would help get more votes! I'd volunteer to do it except I don't have enough Task experience to be confident it would be helpful :)
–
GrahamMcFeb 7 '13 at 12:59

1

one thread would be blocked - but if u r ok with that then no problem. The solution I took was the one below, since no threads are blocked. I read the blog post which was really good.
–
JJschkFeb 8 '13 at 15:14

I'm late to the party, but doesn't the await in all these solutions cause it to block, thus making the asynchronous call synced?
–
KirJul 7 '14 at 21:01

@Kir No it doesn't, in fact using await with Task.WhenAny is what allows it not to block (control is returned to the caller until the awaited task has completed). Perhaps you're thinking of something like Tomas Petricek's solution stackoverflow.com/a/4238575/1512 using Task.WaitAny which does block until one of the tasks has completed.
–
Lawrence JohnstonJul 7 '14 at 21:12

I got a chance to try this finally, and you're right! I'm a little bit confused though - I'm rusty on my Tasks stuff. Why does this work? TimeoutAfter has an await on Task.WhenAny; shouldn't it block until either one of those completes, even when you do not await the TimeoutAfter?
–
KirJul 8 '14 at 13:36

@Kir I don't think I could explain it well in a comment. You should consider opening a new question about it.
–
Lawrence JohnstonJul 9 '14 at 19:38

Use a Timer to handle the message and automatic cancellation. When the Task completes, call Dispose on the timers so that they will never fire. Here is an example; change taskDelay to 500, 1500, or 2500 to see the different cases:

Also, the Async CTP provides a TaskEx.Delay method that will wrap the timers in tasks for you. This can give you more control to do things like set the TaskScheduler for the continuation when the Timer fires.

He doesn't want the current thread to be blocked, that is, no task.Wait().
–
Danny ChenNov 21 '10 at 15:29

@Danny: That was just to make the example complete. After the ContinueWith you could return and let the task run. I'll update my answer to make that more clear.
–
QuartermeisterNov 21 '10 at 15:36

I've updated my question. Could you have a look?
–
dtbNov 21 '10 at 16:04

1

@dtb: What if you make t1 a Task<Task<Result>> and then call TaskExtensions.Unwrap? You can return t2 from your inner lambda, and you can add continuations to the unwrapped task afterwards.
–
QuartermeisterNov 21 '10 at 16:10

Awesome! That perfectly solves my problem. Thanks! I think I will go with the solution proposed by @AS-CII, although I wish I could accept your answer as well for suggesting TaskExtensions.Unwrap Shall I open a new question so you can get the rep you deserve?
–
dtbNov 21 '10 at 16:22

The main advantage of the implementation below is that generics have been added, so the function (or task) can return a value. This means that any existing function can be wrapped in a timeout function, e.g.:

Before:

int x = MyFunc();

After:

// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));

If you use a BlockingCollection to schedule the task, the producer can run the potentially long running task and the consumer can use the TryTake method which has timeout and cancellation token built in.

I'd have to write something up (don't want to put proprietary code here) but the scenario is like this. The producer will be the code that executes the method that could time out and will put the results into the queue when done. The consumer will call trytake() with timeout and will receive the token upon timeout. Both producer and consumer will be backround tasks and display a message to the user using UI thread dispatcher if need be.
–
kns98Feb 11 at 17:28

It seems that we can't resolve this issue by Task<T> itself only. We must use something else to help, such as a System.Threading.Timer. Use the timer to watch the status of your Task<T> and do corresponding actions. This solution seems to be primitive, but it does help in this issue.

@dtb: If you want to get the result as soon as it's finished and can't wait even 0.x seconds, a timer won't help because a timer can only check the task's status every tick.
–
Danny ChenNov 21 '10 at 15:32