Saturday, 30 June 2012

Async Task.Delay

Internal code for Task.Delay (.NET 4.5)

public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
{
if (millisecondsDelay < -1)
{
throw new ArgumentOutOfRangeException("millisecondsDelay", Environment.GetResourceString("Task_Delay_InvalidMillisecondsDelay"));
}
if (cancellationToken.IsCancellationRequested)
{
return FromCancellation(cancellationToken);
}
if (millisecondsDelay == 0)
{
return CompletedTask;
}
DelayPromise state = new DelayPromise(cancellationToken);
if (cancellationToken.CanBeCanceled)
{
state.Registration = cancellationToken.InternalRegisterWithoutEC(delegate (object state) {
((DelayPromise) state).Complete();
}, state);
}
if (millisecondsDelay != -1)
{
state.Timer = new Timer(delegate (object state) {
((DelayPromise) state).Complete();
}, state, millisecondsDelay, -1);
state.Timer.KeepRootedWhileScheduled();
}
return state;
}

The following examples were taken from samples within LinqPad 4.


How to implement Task.Delay in 4.0

/* You can write Task-based asynchronous methods by utilizing a TaskCompletionSource.
A TaskCompletionSource gives you a 'slave' Task that you can manually signal.
Calling SetResult() signals the task as complete, and any continuations kick off. */

void Main()
{
for (int i = 0; i < 10000; i++)
{
Task task = Delay (2000);
task.ContinueWith (_ => "Done".Dump());
}
}

Task Delay (int milliseconds) // Asynchronous NON-BLOCKING method
{
var tcs = new TaskCompletionSource<object>();
new Timer (_ => tcs.SetResult (null)).Change (milliseconds, -1);
return tcs.Task;
}

How NOT to implement Task.Delay 4.0

/* Instead of using a TaskCompletionSource, you can get (seemingly) the same result by
calling Task.Factory.StartNew. This method runs a delegate on a pooled thread,
which is fine for compute-bound work. However, it's not great for I/O-bound work
because you tie up a thread, blocking for the duration of the operation! */

void Main()
{
for (int i = 0; i < 10000; i++)
{
Task task = Delay (2000);
task.ContinueWith (_ => "Done".Dump());
}
}

Task Delay (int milliseconds) // Asynchronous non-blocking wrapper....
{
return Task.Factory.StartNew (() =>
{
Thread.Sleep (2000); // ... around a BLOCKING method!
});
}

// This approach is correct for COMPUTE-BOUND operations.

Returning a Value

// There's also a generic subclass of Task called Task<TResult>. This has a Result
// property which stores a RETURN VALUE of the concurrent operation.

void Main()
{
Task<int> task = GetAnswerToLifeUniverseAndEverything();
task.ContinueWith (_ => task.Result.Dump());
}

Task<int> GetAnswerToLifeUniverseAndEverything () // We're now returning a Task of int
{
var tcs = new TaskCompletionSource<int>(); // Call SetResult with a int instead:
new Timer (_ => tcs.SetResult (42)).Change (3000, -1);
return tcs.Task;
}

// This is great, because most methods return a value of some sort!
//
// You can think of Task<int> as 'an int in the future'.

Dealing with Exceptions

// Tasks also have an Exception property for storing the ERROR should a concurrent 
// operation fail. Calling SetException on a TaskCompletionSource signals as complete
// while populating its Exception property instead of the Result property.

void Main()
{
Task<int> task = GetAnswerToLifeUniverseAndEverything();
task.ContinueWith (_ => task.Exception.InnerException.Dump());
}

Task<int> GetAnswerToLifeUniverseAndEverything()
{
var tcs = new TaskCompletionSource<int>();
new Timer (_ => tcs.SetException (new Exception ("You're not going to like the answer!"))).Change (2000, -1);
return tcs.Task;
}

Example Usage


// Kick off a download operation in the background:
Task<string> task = new WebClient().DownloadStringTaskAsync (new Uri ("http://www.albahari.com/threading"));

// Call the .ContinueWith method to tell a task to do something when it's finished.
task.ContinueWith (_ =>
{
if (task.Exception != null)
task.Exception.InnerException.Dump();
else
{
string html = task.Result;
html.Dump();
}
});

3 comments:

  1. Cool, thanks for Delay in 4.0!

    ReplyDelete
  2. Should you be disposing of Timer in the Task.Delay method?

    ReplyDelete
  3. In addition to quiestion about disposing.
    In this implementation Timer can be collected by GC at any time.

    ReplyDelete