CTU June 2011 - C# 5.0 - ASYNC & Await

Preview:

Citation preview

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

triplez@justinlee.sg

Recommended