I want a given operation to execute for a certain amount of time. When that time has expired, send another execution command.
StartDoingStuff();
System.Threading.Thread.Sleep(200);
StopDoingStuff();
Rather than have a sleep statement in there that's blocking the rest of the application, how can I write this using an Async/Task/Await in C#?
This issue was answered by Joe Hoag in the Parallel Team's blog in 2011: Crafting a Task.TimeoutAfter Method.
The solution uses a TaskCompletionSource and includes several optimizations (12% just by avoiding captures), handles cleanup and covers edge cases like calling TimeoutAfter when the target task has already completed, passing invalid timeouts etc.
The beauty of Task.TimeoutAfter is that it is very easy to compose it with other continuations becaused it does only a single thing: notifies you that the timeout has expired. It doesnt' try to cancel your task. You get to decide what to do when a TimeoutException is thrown.
A quick implementation using async/await
by Stephen Toub is also presented, although edge cases aren't covered as well.
The optimized implementation is:
public static Task TimeoutAfter(this Task task, int millisecondsTimeout)
{
// Short-circuit #1: infinite timeout or task already completed
if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite))
{
// Either the task has already completed or timeout will never occur.
// No proxy necessary.
return task;
}
// tcs.Task will be returned as a proxy to the caller
TaskCompletionSource<VoidTypeStruct> tcs =
new TaskCompletionSource<VoidTypeStruct>();
// Short-circuit #2: zero timeout
if (millisecondsTimeout == 0)
{
// We've already timed out.
tcs.SetException(new TimeoutException());
return tcs.Task;
}
// Set up a timer to complete after the specified timeout period
Timer timer = new Timer(state =>
{
// Recover your state information
var myTcs = (TaskCompletionSource<VoidTypeStruct>)state;
// Fault our proxy with a TimeoutException
myTcs.TrySetException(new TimeoutException());
}, tcs, millisecondsTimeout, Timeout.Infinite);
// Wire up the logic for what happens when source task completes
task.ContinueWith((antecedent, state) =>
{
// Recover our state data
var tuple =
(Tuple<Timer, TaskCompletionSource<VoidTypeStruct>>)state;
// Cancel the Timer
tuple.Item1.Dispose();
// Marshal results to proxy
MarshalTaskResults(antecedent, tuple.Item2);
},
Tuple.Create(timer, tcs),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
return tcs.Task;
}
and Stephen Toub's implementation, without checks for edge cases :
public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
{
if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
await task;
else
throw new TimeoutException();
}
Assuming StartDoingStuff and StopDoingStuff have been created as Async methods returning Task then
await StartDoingStuff();
await Task.Delay(200);
await StopDoingStuff();
EDIT: If the original questioner wants an asynchronous method that will cancel after a specific period: assuming the method would not be making any network requests but just be doing some processing in memory and the outcome can be aborted arbitrarily without considering its effects, then use a cancellation token:
private async Task Go()
{
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(200);
await Task.Run(() => DoIt(source.Token));
}
private void DoIt(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested();
}
}
EDIT: I should have mentioned you can catch the resulting OperationCanceledException providing the indication on how the Task ended, avoiding the need to mess around with bools.
Here's how I'd do it, using task cancellation pattern (the option without throwing an exception).
[EDITED] Updated to use Svick's suggestion to set the timeout via CancellationTokenSource
constructor.
// return true if the job has been done, false if cancelled
async Task<bool> DoSomethingWithTimeoutAsync(int timeout)
{
var tokenSource = new CancellationTokenSource(timeout);
CancellationToken ct = tokenSource.Token;
var doSomethingTask = Task<bool>.Factory.StartNew(() =>
{
Int64 c = 0; // count cycles
bool moreToDo = true;
while (moreToDo)
{
if (ct.IsCancellationRequested)
return false;
// Do some useful work here: counting
Debug.WriteLine(c++);
if (c > 100000)
moreToDo = false; // done counting
}
return true;
}, tokenSource.Token);
return await doSomethingTask;
}
Here's how to call it from an async method:
private async void Form1_Load(object sender, EventArgs e)
{
bool result = await DoSomethingWithTimeoutAsync(3000);
MessageBox.Show("DoSomethingWithTimeout done:" + result); // false if cancelled
}
Here's how to call it from a regular method and handle the completion asynchronously:
private void Form1_Load(object sender, EventArgs e)
{
Task<bool> task = DoSomethingWithTimeoutAsync(3000);
task.ContinueWith(_ =>
{
MessageBox.Show("DoSomethingWithTimeout done:" + task.Result); // false is cancelled
}, TaskScheduler.FromCurrentSynchronizationContext());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With