Powerful Google developer tools for immediate impact! (2023-24 C)
CTU June 2011 - C# 5.0 - ASYNC & Await
1. C# 5 async & await Justin Lee Software Development Consultant Community Technology Update 2011 25th June 2011
2. 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.
3. 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.
4. “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.”
20. // network strings = awaitwebClient.DownloadStringTaskAsync("http://a.com"); strings = awaitwebClient.UploadStringTaskAsync(newUri("http://b"), "dat"); awaitWebRequest.Create("http://a.com").GetResponseAsync(); awaitsocket.ConnectAsync("a.com",80); awaitworkflowApplication.RunAsync(); awaitworkflowApplication.PersistAsync(); PingReply r = awaitping.SendTaskAsync("a.com"); // stream strings = awaittextReader.ReadToEndAsync(); awaitstream.WriteAsync(buffer, 0, 1024); awaitstream.CopyToAsync(stream2); // UI awaitpictureBox.LoadTaskAsync("http://a.com/pic.jpg"); awaitsoundPlayer.LoadTaskAsync(); // task/await, assuming “task” of type IEnumerable<Task<T>> T[] results = awaitTaskEx.WhenAll(tasks); Task<T>winner = awaitTaskEx.WhenAny(tasks); Task<T> task = TaskEx.Run(delegate {... return x;}); awaitTaskEx.Delay(100); awaitTaskEx.Yield(); awaitTaskScheduler.SwitchTo(); awaitDispatcher.SwitchTo(); Ultimately the contents of TaskEx will be moved into Task. How to use the “Task Async Pattern” [TAP]
21. classForm1 : Form { privatevoidbtnGo_Click(object sender, EventArgs e) { cts = newCancellationTokenSource(); cts.CancelAfter(5000); try { awaitnewWebClient().DownloadStringTaskAsync(newUri("http://a.com"), cts.Token); awaitnewWebClient().DownloadStringTaskAsync(newUri("http://b.com"), cts.Token); } catch (OperationCancelledException) { ... } finally {cts= null;} } CancellationTokenSourcects; privatevoidbtnCancel_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
22. classForm1 : Form { privatevoidbtnGo_Click(object sender, EventArgs e) { varcts = newCancellationTokenSource(); cts.CancelAfter(5000); btnCancel.Click += cts.EventHandler; try { // a slicker, more local way to handle cancellation... awaitnewWebClient().DownloadStringTaskAsync(newUri("http://a.com"), cts.Token); awaitnewWebClient().DownloadStringTaskAsync(newUri("http://b.com"), cts.Token); } catch (OperationCancelledException) { ... } finally{btnCancel.Click -= cts.EventHandler;} } } publicstaticclassExtensions { publicstaticvoidEventHandler(thisCancellationTokenSourcects, 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]
23.
24. This parameter is EventProgress<T>, or any other class that implements IProgress<T>... (it’s up to the consumer how to deal with progress)How to use TAP progress
25.
26. PULL techniques are ones where UI thread choses when it wants to pull the next report – e.g. LatestProgress, QueuedProgress.
27. The classes LatestProgresss and QueuedProgress are in the “ProgressAndCancellation” sample in the CTP.How to use TAP progress [advanced]
28. Task<string[]> GetAllAsync(Uri[] uris, CancellationTokencancel, IProgress<int> progress) { var results = newstring[uris.Length]; for (int i=0; i<uris.Length; i++) { cancel.ThrowIfCancellationRequested(); results[i] = awaitnewWebClient().DownloadStringTaskAsync(uris[i], cancel); if (progress!=null) progress.Report(i); } return results; } 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. 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... Pass cancellation down: usually it will be appropriate to pass the cancellation down to nested async functions that you call. 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
29. Task Delay(intms, CancellationTokencancel); 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 query int[] results = awaitTaskEx.WhenAll(fromurlinurlsselectGetIntAsync(url)); // WhenAny to implement a concurrent worker pool Queue<string> todo= ...; var workers = newHashSet<Task<int>>(); for(inti=0; i<10; i++) workers.Add(GetIntAsync(todo.Dequeue()); while (workers.Count>0) { var winner = awaitTaskEx.WhenAny(workers); Console.WriteLine(await winner); workers.Remove(winner); if (todo.Count>0) workers.Add(GetIntAsync(todo.Dequeue()); } Task<T> combinators
30. asyncvoidFireAndForgetAsync() { await t; } asyncTask MerelySignalCompletionAsync() { return; } AsyncTask<int> GiveResultAsync() { return 15; } FireAndForgetAsync(); awaitMerelySignalCompletionAsync(); varr = awaitGiveResultAsync(); 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. Task-returning asyncs: Used if you merely want to know when the task has finished. Exceptions get squirrelled away inside the resultant Task. Task(Of T)-returning asyncs: Used if you want to know the result as well. Three kinds of asyncmethod
31. // Task Asynchronous Pattern [TAP], with Cancellation and Progress Task<TR> GetStringAsync(Params..., [CancellationTokenCancel], [IProgress<TP>Progress]) // Asynchronous Programming Model [APM]IAsyncResultBeginGetString(Params..., AsyncCallbackCallback, object state);TR EndGetString(IAsyncResult); // Event-based Asynchronous Pattern [EAP]classC { publicvoidGetStringAsync(Params...); publiceventGetStringCompletedEventHandlerGetStringCompleted; publicvoidCancelAsync(); } classGetStringCompletedEventArgs { publicTR Result { get; } publicException Error { get; } } Comparing TAP to its predecessors
* Let's talk about this with waiters.* Read slowly: "A waiters job..."* Flaw in this. Can you see what it is? Obviously, a waiter can interleave!
[Joke: oh, you don’t want to go home early? Great. In that case I’ll dive into how it actually worked under the hood.]GOALS: To understand the “await” feature at a professional level -- that is, where the flow-of-control goes, which threads are involved, and how they communicate. Also to understand more deeply what asynchrony is. This will be enable you to be a better architect of asynchronous programs, e.g. Silverlight and ASP.This is the demo code, but I simplified it a little. I also split up “GetDiggAsync” and “await” onto separate lines. (Also I ported it over mostly to C#, apart from the XML literals).There are two existing threads allocated by the operating system. One is the UI thread for this process. The other is the “IO Completion Port” thread. Each of these threads has a queue associated with it.
When the user clicks a button, this inserts a “button-click-message” into the UI queue.The UI thread is in a while loop, checking for messages. When it gets this message it invokes the button-click handler.
The same thread makes function calls.When it calls the API “web.DownloadStringTaskAsync”, this returns immediately. It returns a Task, which I’ve called “downTask”. This task has not yet completed. The network stack will know to mark it as completed once the server’s response comes back.I should stress that “Task” does not mean a “BackgroundThreadTask”. The Task class is unrelated to questions of threads of execution.A “Task” is merely a “future” (C++), a “promise” – it’s an object that exists in one of three states, “InProgress” or “Completed(with result)” or “Faulted(with exception)”. It can be caused to transition from the first state to either of the other two. When this transition takes place, it will invoke any continuations that have been registered with it.And Task is a great unifying abstraction. That’s because it can stand for so many things – for a background worker thread on the current machine, or for a thread of execution on some remote database server, or for things that don’t take any threads at all like a button-click or a DMA transfer from disk to memory.Actually, if you’re familiar with the TPL, Task has a third state “Cancelled”. Through APIs we end up treating this state as equivalent to Faulted(with OperationCancelledException).
Now we execute the “await” operator. This does two things.First, it signs up a continuation onto downTask. For now I’ve written the continuation as “ui.Post{K1}” – not in real syntax. We’ll see later what it does. (Note that a task is allowed to have many continuations signed up on it.)Next, the first time we execute the “await” operator in an async method, we return a Task immediately. Once again, this task has not yet completed.
We execute the “await” operator. once again, this signs up a continuation onto the task, and returns to the calling thread (the UI thread).The UI thread can now resume it’s “while”-loop, checking for messages.If any other button-clicks happened (or mouse-drags or repaints or window-resizing) now, then they could be dealt with by the UI thread fine. This is where responsiveness comes in. (but it also brings in re-entrancy... imagine if the user clicked the same button again! then we’d start a second concurrent run through this button1_Click handler!)
Hey! A few seconds later, and the web services has delivered its answer to the IO Completion Port thread!
The IOCP thread knows which task was associated with that response.So it transitions it from the “Running” state to the “Completed with result ‘rss’” state. This causes it to execute the task’s continuation.
The continuation merely adds a message into the UI queue. Then it returns, allowing the IO Completion Port thread to resume its work.
The UI thread picks up the message from its queue, and responds by dispatching to K1, which assigns into the variable “rss”.
The code continues to execute. it comes to the “return” statement.(note that we have already returned the Task<string> “diggTask” from this method. So you know the return statement is going to do something different...)
The “return” statement, in an async method, sets its returned task’s state to “Completed”, provides a result, and executes the task’s continuation.Once again, the continuation merely posts to the UI’s message-queue.
The method returns. Now the UI-thread can go back to its “while” loop, checking for messages in the queue. (There is one already!)
And the UI thread executes the method, puts the story into the text-box, and finishes.Note that ALL user code executed on the UI thread. All of it. That means the user never had to worry about the typical multi-threaded problems (semaphores, mutexes, semaphores, races, locks, ...)Also count how many threads were involved. Just the two that were already provided by the operating system. We didn’t create ANY additional threads.
These are some of the “Task Async Pattern” APIs that are included in the CTP.
We initially hadvar task2 = task.TimeoutAfter(1000);But we removed it because it didn’t have a clear-enough design. Would you want task2 to end with an OperationCancelledException after 1000ms? Or to end successfully? Both forms are useful. In the end, we figured that cancellation-after-1000ms was easier done like this:varcts = new CancellationTokenSource();cts.CancelAfter(1000)And we figured that successful-termination-after-1000ms was clearer if you wrote it out manually:var task2 = task.WhenAny(task, Task.Delay(1000))
There are three kinds of async methods. The difference between them is subtle.The difference between them is so subtle that we considered eliminating the first kind “void-returning asyncs” entirely. But that would have been wrong. That’s because every single async method you write will be invoked by someone who is also async, all the way up to the very top level of the callstack, to the “fire-and-forget” event handlers at the top like Button1_Click().So: every program that you’ll ever write will use void-returning asyncs. We have to accept that, and include it as a language feature.
Something to note here is that Task is composable.For instance, you can write a method which takes any two tasks and awaits until they’re both done.You can’t do that with the APM or the EAP. That’s because APM is just “a pair of methods with arbitrary parameters” and WebClient is just “a set of methods and events you have to call”. They’re not first-class citizens. They’re not things that you can pass as parameters to another method. But you can pass a Task directly to another method.That’s why the TAP is better.