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();
}
});

No comments:

Post a Comment