Async
The .net libraries have higher-level APIs for async work (so you don't have to manage the low level Threading).
- .Net 4: Task Parallel Library and see below
- .Net 4.5: Async
Asynchronous patterns in FCL
There are 3 main patterns in .net:
- APM: Asynchronous Programming Model (BeginX, EndX(IAsyncResult), with AsyncCallbacks)
- EAP: Event based Asynchronous Pattern (callback events, eg BackgroundWorker)
- TAP: Task-based Asynchronous Pattern (Task Programming Library in .net 4)
The await/async pattern in .net 4.5 extends this so that asynchronous code is written like normal synchronous code.
NB: concurrency (parallelism) isn't quite the same as asynchronous, but the Task combination methods .WhenAll and .WhenAny enable concurrency.
Task
Task is a higher level API for managing threads, and is enhanced in .net 4.5 with awaiters.
Task corresponds to what other APIs call a "Future" or "Promise".
Threads | Tasks |
---|---|
new Thread(Method).Start(); |
//.net 4.0 Task.Factory.StartNew(Method); //.net 4.5 Task.Run(Method); Tasks are always Started.
|
Thread.Sleep(1000); | Task.Delay(1000); //.net 4.5 |
thread.Join(); | task.Wait(); |
- You can turn APM into awaitable Tasks with the .AsTask() extension.
- For WinForms/WPF, you're always in the main UI context, so there are no problems referencing controls (you can mark Tasks with .ConfigureAwait(false) if UI is not required).
Exceptions
An async task may throw either an AggregateException or a TaskCancelledException.
But it is possible to have a void-return to not be captured... and uncaptured exceptions will crash the app when garbage collected.
- Use a global exception handler
- Async has it's own unhandled exception event: TaskScheduler.UnobservedTaskException
.Net 4.0 Task Parallel Library
//separate creation from scheduling
//var task1 = new Task<int>(() => int.MaxValue);
//task1.Start();
//preferred
var task = Task.Factory.StartNew(() => int.MaxValue);
try
{
//block with a timeout
if (!task.Wait(TimeSpan.FromSeconds(1)))
{
Debug.WriteLine("It took longer than a second");
}
return task.Result;
}
catch (AggregateException exception)
{
// exception.InnerExceptions can include nested AggregateExceptions
// so exception.Flatten() to simplify
exception.Handle(x =>
{
if (x is OverflowException)
{
Console.WriteLine("Overflow handled");
return true;
}
return false;
});
}
Func Results
var task = Task<int>.Factory.StartNew(() => 1 + 2);
int result = task.Result; //this blocks- like task.Wait()
Multiple tasks
var tasks = new Task[2]
{
Task.Factory.StartNew(Work1),
Task.Factory.StartNew(Work2)
};
//Block until all tasks complete.
Task.WaitAll(tasks);
- Creation: you can create with Factory.StartNew (preferred), or various Action/Func ctors.
- Waiting: Task.WaitAll(tasks), Task.WaitAny(tasks), task.Wait with timespan.
- Exceptions: Everything is nested in AggregateException (thrown on Waits or accessing Result)
Continuations
var continuationTask = Task.Factory
.StartNew(() => ReadFile())
.ContinueWith(x => ReadLines(x.Result));
Also ContinueWhenAll, ContinueWhenAny
Cancellations
MSDN. Pass in a cancellation token and cancel the token source on the main thread.
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task = Task.Factory.StartNew(() =>
{
token.ThrowIfCancellationRequested();
for (var i = 0; i < 3; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
//raise an OperationCanceledException
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
}
//pass in the token
}, token);
//cancel it
tokenSource.Cancel();
//run it
try
{
task.Wait();
}
catch (AggregateException)
{
//will hit here
}
Console.WriteLine(task.IsCanceled);
Async- await (.net 4.5)
await makes the rest of the method a continuation. You can't use it in catch/finally blocks.
await Task.Run(() => 1 + 2);
//You must also mark lambdas "async" when they include an await...
var result = await Task.Run(async () =>
{
await Task.Delay(1000);
return CpuIntensive();
});
- Await says to wait for a method that is marked async (similar to task.Wait, but makes rest of method into a callback)
A void return cannot be awaited- it's a fire and forget.
await = "pause here until completed" - the thread is not blocked - Async method keyword tells the compiler the method has an await.(there's a compiler warning if it doesn't).
An async method may return void, Task or Task<T> (in WinRT, IAsyncOperation<T>). - By convention, the method name has the suffix Async
You can also store and await later (concurrency:)
var task1 = RunAsync();
var task2 = Run2Async();
Task.WaitAll(new Task[] { task1, task2 });
//or
await Task.WhenAll(task1, task2);
var result1 = task1.Result;
var result2 = task2.Result;
Or, more simply:
var task1 = RunAsync();
var task2 = Run2Async();
//tasks are now running simultaneously
var result1 = await task1; //wait for first task to finish
var result2 = await task2; //wait for second
Prevent reentrancy
You can cache the Task... Note no async/await here.
private static Dictionary<string, Task<string>> _cache =
new Dictionary<string, Task<string>>();
private static Task<string> GetPageAsync(string uri) //no "async"
{
Task<string> task;
if (_cache.TryGetValue(uri, out task)) return task;
//no "await" keyword- set the dictionary value to the task
return _cache[uri] = new HttpClient().GetStringAsync(uri);
}
Cancellation
You can create a Task with a Task.Delay(timeout) which then fires a cancellationToken. But in .net 4.5 it is simpler to just set the timeout in the CancellationTokenSource ctor.
//timeout of one second
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
var get = await new HttpClient().GetAsync(url, cts.Token);
get.EnsureSuccessStatusCode();
var response = await get.Content.ReadAsStringAsync();
Wrap in try/catch(OperationCancelledException) Then cts.Cancel(); to cancel.
NB: WInRT doesn't have tokens, but you can use the extension method .AsTask(token) to turn it into standard .net.
CancellationToken is a struct, so in implementing methods the input argument should get an empty token
private async static Task<string> RunAsync(
CancellationToken token = default(CancellationToken))
Progress
You can also use Progress<T>.Report(value) for progress reporting.
//create the progress with an Action (eg update UI progress bar)
var progress = new Progress<int>(i => Console.WriteLine(i));
var task1 = RunAsync(progress);
private async static Task<string> RunAsync(IProgress<int> progress,
CancellationToken token = default(CancellationToken))
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(400);
progress.Report(i);
}
return "Done";
}
Async void
Testing
MSTest supports async Task testmethods since VS2012. When using mocks, use Task.FromResult(x)
var moq = new Mock<IService>();
moq.Setup(x => x.Save(It.IsAny<Message>())).Returns(Task.FromResult((int?)1));