ctu june 2011 - c# 5.0 - async & await
TRANSCRIPT
C# 5 async & await
Justin LeeSoftware Development ConsultantCommunity Technology Update 201125th June 2011
Concurrency is…
about running or appearing to run two things at once, through cooperative or pre-emptive multitasking or multicore.
Good reasons to use concurrency:
• for the CPU-bound multicore computational kernel (e.g. codecs);
• for a server handling requests from different processes/machines;
• to “bet on more than one horse” and use whichever was fastest.
Asynchrony is…
about results that are delayed, and yielding control while awaiting them (co-operative multitasking).
Good reasons to use asynchrony:
• for overall control / coordination structure of a program;
• for UI responsiveness;
• for IO- and network-bound code;
• for coordinating your CPU-bound multicore computational kernel.
“A waiter’s job is to wait on a table until the patrons have finished their meal.
If you want to serve two tables concurrently, you must hire two waiters.”
DemoConverting from synchronous,
to asynchronous, to using await
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
UIthread IOCP
threadHow the demo actually worked
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
[1/12] A button-click arrives on the UI queue
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
dTask
[2/12] Invoke some functions; get back “dTask” from the API
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var task = web.DownTaskAsync("http://netflix.com");
var rss = await task;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
dTask
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
UIthread IOCP
thread
Click
dTask » ui.Post{Κ1}
Κ1:
[3/12] “await task” assigns a continuation and returns task
task
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
tasktask » ui.Post{Κ2}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
dTask » ui.Post{Κ1}
Κ1:
Κ2:
[4/12] “await task” assigns a continuation and returns
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
rss
dTask » ui.Post{Κ1}
Κ1:
Κ2:
task » ui.Post{Κ2}
[5/12] Network packet arrives with data
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
rssui.Post{Κ1(rss)}
dTask » ui.Post{Κ1}
Κ1:
Κ2:
task » ui.Post{Κ2}
[6/12] Invoke dTask’s continuation with that data
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
task » ui.Post{Κ2}
rss
K1(rss) Κ1:
Κ2:
ui.Post{Κ1(rss)}
[7/12] Continuation is a “Post”, i.e. addition to the UI queue
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
rss
K1(rss) Κ1:
Κ2:
ui.Post{Κ1(rss)}
task » ui.Post{Κ2}
[8/12] UI thread executes K1, giving a result to the “await”
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
rss
K1(rss) Κ1:
Κ2:
ui.Post{Κ1(rss)}
task » ui.Post{Κ2}
[9/12] “return movies” will signal completion of task
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
rss
K1(rss)
ui.Post(Κ2(movie))
K2(movie)
Κ1:
Κ2:
ui.Post{Κ1(rss)}
task » ui.Post{Κ2}
[10/12] Invoke task’s continuation with data (by posting to UI queue)
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
UIthread IOCP
thread
Click
rss
K1(rss)
ui.Post(Κ2(movie))
K2(movie)
Κ1:
Κ2:
ui.Post{Κ1(rss)}
[11/12] Return from handling the K1 continuation
async void SearchButtonClick()
{ var task = QueryMoviesAsync();
var m = await task;
textBox1.Text = movies;
}
async Task<string> LoadMoviesAsync()
{ var web = new WebClient();
var dTask = web.DownTaskAsync("http://netflix.com");
var rss = await dTask ;
var movies = XElement.Parse(rss).<story>.<description>;
return movies;
}
rss
K2(movie)
Click
K1(rss)
UIthread IOCP
thread
ui.Post(Κ2(movie))
Κ1:
Κ2:
ui.Post{Κ1(rss)}
[12/12] UI thread executes K2, giving a result to the “await”
DemoUsing await and async with Silverlight
// networkstring s = await webClient.DownloadStringTaskAsync("http://a.com");string s = await webClient.UploadStringTaskAsync(new Uri("http://b"), "dat");await WebRequest.Create("http://a.com").GetResponseAsync();await socket.ConnectAsync("a.com",80);await workflowApplication.RunAsync();await workflowApplication.PersistAsync();PingReply r = await ping.SendTaskAsync("a.com");
// streamstring s = await textReader.ReadToEndAsync();await stream.WriteAsync(buffer, 0, 1024);await stream.CopyToAsync(stream2);
// UIawait pictureBox.LoadTaskAsync("http://a.com/pic.jpg");await soundPlayer.LoadTaskAsync();
// task/await, assuming “task” of type IEnumerable<Task<T>>T[] results = await TaskEx.WhenAll(tasks);Task<T> winner = await TaskEx.WhenAny(tasks);Task<T> task = TaskEx.Run(delegate {... return x;});await TaskEx.Delay(100);await TaskEx.Yield();await TaskScheduler.SwitchTo();await Dispatcher.SwitchTo();
Ultimately the contents of TaskEx will be moved into Task.
How to use the “Task Async Pattern” [TAP]
class Form1 : Form{ private void btnGo_Click(object sender, EventArgs e) { cts = new CancellationTokenSource(); cts.CancelAfter(5000); try { await new WebClient().DownloadStringTaskAsync(new Uri("http://a.com"), cts.Token); await new WebClient().DownloadStringTaskAsync(new Uri("http://b.com"), cts.Token); } catch (OperationCancelledException) { ... } finally {cts = null;} }
CancellationTokenSource cts;
private void btnCancel_Click(object sender, EventArgs e) { if (cts!=null) cts.Cancel(); }}This is the proposed new standard framework pattern for cancellation.
Note that cancellation token is able to cancel the current operation in an async sequence; or it can cancel several concurrent async operations; or you can take it as a parameter in your own async methods and pass it on to sub-methods. It is a “composable” way of doing cancellation.
How to use TAP cancellation
class Form1 : Form{ private void btnGo_Click(object sender, EventArgs e) { var cts = new CancellationTokenSource(); cts.CancelAfter(5000); btnCancel.Click += cts.EventHandler; try { // a slicker, more local way to handle cancellation... await new WebClient().DownloadStringTaskAsync(new Uri("http://a.com"), cts.Token); await new WebClient().DownloadStringTaskAsync(new Uri("http://b.com"), cts.Token); } catch (OperationCancelledException) { ... } finally {btnCancel.Click -= cts.EventHandler;} }}
public static class Extensions{ public static void EventHandler(this CancellationTokenSource cts, object _, EventArgs e) { cts.Cancel(); }}In this version, we keep “cts” local to just the operation it controls.
Note that “cts” can’t be re-used: once it has been cancelled, it remains cancelled. That’s why we create a new one each time the user clicks “Go”.
A good idea: btnGo.Enabled=false; btnCancel.Enabled=true;
How to use TAP cancellation [advanced]
private void btnGo_Click(object sender, EventArgs e){ var progress = new EventProgress<DownloadProgressChangedEventArgs>();
// Set up a progress-event-handler (which will always fire on the UI thread, // even if we'd launched the task ona different thread). progress.ProgressChanged += (_, ee) => { progressBar1.Value = ee.Value.ProgressPercentage; };
// Wait for the task to finish await new WebClient().DownloadStringTaskAsync(uri, cts.Token, progress);}
This is the proposed new standard framework pattern for progress-reporting (for those APIs that support progress-reporting).
• The user passes in a “progress” parameter• This parameter is EventProgress<T>, or any other class that implements
IProgress<T>... (it’s up to the consumer how to deal with progress)
interface IProgress<T>{ void Report(T value);}
How to use TAP progress
// handle progress with a "while" loop, instead of a callback:var progress = new LatestProgress<DownloadProgressChangedEventArgs>();var task = new WebClient().DownloadStringTaskAsync(uri, cts.Token, progress);
while (await progress.Progress(task)){ progressBar1.Value = progress.Latest.ProgressPercentage;}
// another “while” loop, except this one queues up reports so we don’t lose any:var progress = new QueuedProgress<DownloadProgressChangedEventArgs>();var task = new WebClient().DownloadStringTaskAsync(uri, cts.Token, progress);
while (await progress.NextProgress(task)){ progressBar1.Value = progress.Current.ProgressPercentage;}
• PUSH techniques are ones where the task invokes a callback/handler whenever the task wants to – e.g. EventProgress, IObservable.
• PULL techniques are ones where UI thread choses when it wants to pull the next report – e.g. LatestProgress, QueuedProgress.
• The classes LatestProgresss and QueuedProgress are in the “ProgressAndCancellation” sample in the CTP.
How to use TAP progress [advanced]
Task<string[]> GetAllAsync(Uri[] uris, CancellationToken cancel, IProgress<int> progress){ var results = new string[uris.Length]; for (int i=0; i<uris.Length; i++) { cancel.ThrowIfCancellationRequested(); results[i] = await new WebClient().DownloadStringTaskAsync(uris[i], cancel); if (progress!=null) progress.Report(i); } return results;}
1. Take Cancel/progress parameters: If your API supports both cancellation and progress, add a single overload which takes both. If it supports just one, add a single overload which takes it.
2. Listen for cancellation: either do the pull technique of “cancel.ThrowIfCancellationRequested()” in your inner loop, or the push technique of “cancel.Register(Action)” to be notified of cancellation, or...
3. Pass cancellation down: usually it will be appropriate to pass the cancellation down to nested async functions that you call.
4. Report progress: in your inner loop, as often as makes sense, report progress. The argument to progress.Report(i) may be read from a different thread, so make sure it’s either read-only or threadsafe.
How to implement TAP cancellation/progress
Task Delay(int ms, CancellationToken cancel);
Task<T> Run<T>(Func<T> function);
Task<IEnumerable<T>> WhenAll<T>(IEnumerable<Task<T>> tasks);
Task<Task<T>> WhenAny<T>(IEnumerable<Task<T>> tasks);// WhenAny is like Select. When you await it, you get the task that “won”.
// WhenAll over a LINQ queryint[] results = await TaskEx.WhenAll(from url in urls select GetIntAsync(url));
// WhenAny to implement a concurrent worker poolQueue<string> todo = ...;var workers = new HashSet<Task<int>>();for (int i=0; i<10; i++) workers.Add(GetIntAsync(todo.Dequeue());while (workers.Count>0){ var winner = await TaskEx.WhenAny(workers); Console.WriteLine(await winner); workers.Remove(winner); if (todo.Count>0) workers.Add(GetIntAsync(todo.Dequeue());}
Task<T> combinators
async void FireAndForgetAsync() { await t;}
async Task MerelySignalCompletionAsync() { return;}
Async Task<int> GiveResultAsync() { return 15;}
FireAndForgetAsync();
await MerelySignalCompletionAsync();
var r = await GiveResultAsync();
1. Async subs (“void-returning asyncs”): used for “fire-and-forget” scenarios. Control will return to the caller after the first Await. But once “t” has finished, the continuation will be posted to the current synchronization context. Any exceptions will be thrown on that context.
2. Task-returning asyncs: Used if you merely want to know when the task has finished. Exceptions get squirrelled away inside the resultant Task.
3. Task(Of T)-returning asyncs: Used if you want to know the result as well.
Three kinds of async method
// Task Asynchronous Pattern [TAP], with Cancellation and ProgressTask<TR> GetStringAsync(Params..., [CancellationToken Cancel], [IProgress<TP> Progress])
// Asynchronous Programming Model [APM]IAsyncResult BeginGetString(Params..., AsyncCallback Callback, object state);TR EndGetString(IAsyncResult);
// Event-based Asynchronous Pattern [EAP]class C{ public void GetStringAsync(Params...); public event GetStringCompletedEventHandler GetStringCompleted; public void CancelAsync();}
class GetStringCompletedEventArgs{ public TR Result { get; } public Exception Error { get; }}
Comparing TAP to its predecessors
DemoProgress and Cancellation
DemoAsync on Windows Phone 7
(if there’s time)
Q & A