“a language+framework push for compositional asynchrony” Async.NET Working Group, F#, Axum, Task Avner Aharoni, Mads Torgersen, Stephen Toub, Alex Turner, Lucian Wischik
“a language+framework push for compositional asynchrony” 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. 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.
“a language+framework push for compositional asynchrony” 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. 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. Bad reasons to use concurrency: “You should stick IO on a background thread to avoid blocking the UI” “Asynchrony makes your program structure too complex”
The following is wrong. Can you spot the flaw? “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.”
The following is from the Android developer blog. Can you spot the flaw? “A good practice in creating responsive applications is to make sure your main UI thread does the minimum amount of work. Any potentially long task that may hang your application should be handled in a different thread. Typical examples of such tasks are network operations, which involve unpredictable delays.”
Outline Of Talk 1. Demoof simple end-user experience with CTP. Threading story. 2. Language specification of “await” keyword through compiler rewrites. 3. Frameworkthe new “Task Asynchronous Pattern”; combinators; ecosystem 4. Designhot vs cold; async blocks vs methods; IAsyncEnumerable 5. Theorythe academic theory around async – callcc? co-monads? “a language+framework push for compositional asynchrony” 1. Demo1. Demo /th/th 2. Language 3. Framework 4. Design 5. Theory
demo “a language+framework push for compositional asynchrony” 1. Demo /th 1. Demo/th 2. Language 3. Framework 4. Design 5. Theory
This is a very simple WPF application. When you click the button, it retrieves the top Digg news story and its most recent comment. My task: port it to Silverlight
Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click Try Dim story = GetDiggStory() textBox1.Text = story.Description textBox2.Text = GetDiggComment(story.Id) Catch ex As Exception textBox1.Text = ex.ToString End Try End Sub Function GetDiggStory() As DiggStory Dim web As New WebClient Dim rss = web.DownloadString(new Uri(" ")) Dim story = XElement.Parse(rss). Return New DiggStory With } End Function Function GetDiggComment() As String Dim web As New WebClient Dim rss = web.DownloadString(new Uri(" & id)) Return XElement.Parse(rss)..Value End Function Class DiggStory Public Id As String Public Description As String End Class This is the original WPF code.[1/10]
If you try to compile it on Silverlight, it doesn’t work. That’s because Silverlight lacks “DownloadString” API. “DownloadString” is synchronous – it blocks the UI, making it non-responsive. Silverlight chose not to have it, because it’s bad to block the UI of the web-browser. [1/10]
It was hard to make this code asynchronous before Async... “a language+framework push for compositional asynchrony”
[2/10] Problem: DownloadString doesn’t exist [2/10]
Solution: use DownloadStringAsync instead (which merely initiates the web- request). And add an event-handler for when the server eventually comes back with a response. [2/10]
Problem: now the “Return” statement doesn’t work. That’s because it now returns from the inner Sub, not the outer function. “Callbacks/events do not compose with the Return statement.” [3/10]
Solution: deliver back the result of GetDiggStory through a callback of our own. [3/10]
Problem: we’re not handling the asynchronous error case. * DownloadStringAsync might give an exception immediately if it’s unable to make the request. * Or, if the server responds with an HTTP error code, then we’ll get the exception back in our handler. [4/10]
Solution: give back error information in two places: either from the exception of DownloadStringAsync, or in our callback, depending on where the error came from. “Callbacks/events do not compose with the throwing of exceptions.” [4/10]
Problem: now we have to update Button1_Click, since it invokes functions that now take callbacks. [5/10]
Solution: instead of using semicolon (C#) or linebreak (VB) to separate one statement from the next, we have to use a nested lambda. “Callbacks/events do not compose with the semicolon operator.” [5/10]
Problem: we have to fix up error- handling as well, since errors might come either through an exception or through the callback. “Callbacks/events do not compose with exception handling.” (nor with Using, nor with For/While loops) [6/10]
Solution: use explicit error checks in addition to the exception handling. The code basically has to be duplicated. [6/10]
The Async CTP offers a better way to make this code asynchronous. “a language+framework push for compositional asynchrony”
Problem: these methods need to be made asynchronous. [7/10]
Solution part 1: Mark these methods as “Async” and change their return types to Task(Of...). By convention, all async methods in the framework have names ending in “Async”. [8/10]
Solution part 2: “Await” the Task- Asynchronous versions of all calls, instead of invoking the synchronous versions. [9/10]
Solution part 3: Because Button1_Click has an “Await” in it, it too must be marked Async. (The Async modifier has to “bubble-up” the call hierarchy.) [10/10]
My task: port it to Silverlight Status: finished ahead of schedule! I think I’ll go home early today.
async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } UI thread IOCP thread How the demo actually worked 1. Demo /th 1. Demo/th 2. Language 3. Framework 4. Design 5. Theory
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click [1/12] A button-click arrives on the UI queue
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click downTask [2/12] Invoke some functions; get back “downTask” from the API
async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } downTask async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } UI thread IOCP thread Click downTask » ui.Post{Κ1} [3/12] “await downTask” assigns a continuation and returns diggTask Κ1: diggTask
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } diggTask async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click downTask » ui.Post{Κ1} [4/12] “await diggTask” assigns a continuation and returns Κ1: Κ2: diggTask » ui.Post{Κ2}
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click rss [5/12] Network packet arrives with data downTask » ui.Post{Κ1} Κ1: Κ2: diggTask » ui.Post{Κ2}
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click rss ui.Post{Κ1(rss)} [6/12] Invoke downTask’s continuation with that data downTask » ui.Post{Κ1} Κ1: Κ2: diggTask » ui.Post{Κ2}
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click diggTask » ui.Post{Κ2} rss K1(rss) [7/12] Continuation is a “Post”, i.e. addition to the UI queue Κ1: Κ2: ui.Post{Κ1(rss)}
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click rss K1(rss) [8/12] UI thread executes K1, giving a result to the “await” Κ1: Κ2: ui.Post{Κ1(rss)} diggTask » ui.Post{Κ2}
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click rss K1(rss) [9/12] “Return story” will signal completion of task Κ1: Κ2: ui.Post{Κ1(rss)} diggTask » ui.Post{Κ2}
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click rss K1(rss) ui.Post(Κ2(story)) [10/12] Invoke diggTask’s continuation with data (by posting to UI queue) K2(story) Κ1: Κ2: ui.Post{Κ1(rss)} diggTask » ui.Post{Κ2}
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } UI thread IOCP thread Click rss K1(rss) ui.Post(Κ2(story)) K2(story) [11/12] Return from handling the K1 continuation Κ1: Κ2: ui.Post{Κ1(rss)}
async void button1_Click() { var diggTask = GetDiggAsync(); var digg = await diggTask; textBox1.Text = digg; } async Task GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync(" var rss = await downTask; var digg = XElement.Parse(rss).. ; return digg; } rss K2(story) Click K1(rss) UI thread IOCP thread ui.Post(Κ2(story)) Κ1: Κ2: [12/12] UI thread executes K2, giving a result to the “await” ui.Post{Κ1(rss)}
SINGLE-THREADED ASYNCHRONY AND CONCURRENCY is when we run asynchronous and concurrent tasks with NO additional threads (beyond those that the operating system already provides). No worries about mutexes &c. If you want extra threads, create them explicitly through Task.Run.
language “a language+framework push for compositional asynchrony” 1. Demo1. Demo /th/th 2. Language 2. Language 3. Framework 4. Design 5. Theory
async Task GetDiggAsync(int p) { var x = await t; return x; } 2. Language feature, explained as a syntactic rewrite Over the following slides we’ll see how the compiler rewrites this async method…
async Task GetDiggAsync(int p) { var x = await t; return x; } 2. Language [1/4] _temp = t.GetAwaiter(); _temp.BeginAwait(K1); return; K1: var x = _temp.EndAwait(); _temp = t.GetAwaiter(); _temp.BeginAwait(K1); return; K1: var x = _temp.EndAwait(); The type of _temp is whatever t.GetAwaiter() returns. This is a syntactic expansion: it binds using normal language rules (overload resolution, extension methods, …). TaskAwaiter’s implementation resumes back on the same SynchronizationContext. TaskAwaiter _temp;
async Task GetDiggAsync(int p) { var x = await t; return x; } 2. Language [1/4] _temp = t.GetAwaiter(); if (_temp.BeginAwait(K1)) return; K1: var x = _temp.EndAwait(); _temp = t.GetAwaiter(); if (_temp.BeginAwait(K1)) return; K1: var x = _temp.EndAwait(); Actually we use an “if” statement to allow a fast-path: if “e” had already finished, it can decide to skip all the continuation machinery. TaskAwaiter _temp;
async Task GetDiggAsync(int p) { TaskAwaiter _temp; _temp = t.GetAwaiter(); if (_temp.BeginAwait(K1)) return; K1: var x = _temp.EndAwait(); return x; } 2. Language [1/4]
async Task GetDiggAsync(int p) { TaskAwaiter _temp; _temp = t.GetAwaiter(); if (_temp.BeginAwait(K1)) return; K1: var x = _temp.EndAwait(); return x; } 2. Language [2/4] var _builder = AsyncMethodBuilder.Create(); int _state = 0; Action _moveNext = delegate {... var _builder = AsyncMethodBuilder.Create(); int _state = 0; Action _moveNext = delegate { } _moveNext(); return _builder.Task;... } _moveNext(); return _builder.Task; System.Runtime.CompilerServices.AsyncMethodBuilder
async Task GetDiggAsync(int p) { var _builder = AsyncMethodBuilder.Create(); TaskAwaiter _temp; int _state = 0; Action _moveNext = delegate { _temp = t.GetAwaiter(); if (_temp.BeginAwait(K1)) return; K1: var x = _temp.EndAwait(); return x; } _moveNext(); return _builder.Task; } 2. Language [2/4]
async Task GetDiggAsync(int p) { var _builder = AsyncMethodBuilder.Create(); TaskAwaiter _temp; int _state = 0; Action _moveNext = delegate { _temp = t.GetAwaiter(); if (_temp.BeginAwait(K1)) return; K1: var x = _temp.EndAwait(); return x; } _moveNext(); return _builder.Task; } 2. Language [3/4] try { //... jump table based on _state try { //... jump table based on _state _builder.SetResult(r); return; catch (Exception ex) { _builder.SetException(ex); } _moveNext _state = 1; _state = 0;
async Task GetDiggAsync(int p) { var _builder = AsyncMethodBuilder.Create(); TaskAwaiter _temp; int _state = 0; Action _moveNext = delegate { try { //... jump table based on _state _temp = t.GetAwaiter(); _state = 1; if (_temp.BeginAwait(_moveNext)) return; K1: _state = 0; var x = _temp.EndAwait(); _builder.SetResult(x); return; } catch (Exception ex) { _builder.SetException(ex); } _moveNext(); return _builder.Task; } 2. Language [3/4]
The C# and VB languages know NOTHING about threading. All the threading policies we saw earlier come from bindings provided by the framework type Task.GetAwaiter(): they are not baked into the language. Any expression e can be awaited, if it has the right bindings: var temp = e.GetAwaiter() bool b = temp.BeginAwait(Action) e.EndAwait() The kind of things you await can be completely different from the Tasks you get back from an async method. All “await” bindings can be provided by extension methods. “a language+framework push for compositional asynchrony”
2. EXAMPLE: add “awaitability” to a Button’s click event
2. Language [4/4] try { //... jump table based on _state if (BeginAwait(...)) {return;} K1: EndAwait() try { if (BeginAwait(...)) {return;} K2: EndAwait() } finally {... } catch (Exception ex) { _builder.SetException(ex); }
2. Language [4/4] try { //... jump table based on _state if (BeginAwait(...)) {return;} K1: EndAwait() try { if (BeginAwait(...)) {return;} K2: EndAwait() } finally {... } catch (Exception ex) { _builder.SetException(ex); } bool bypassFinally = false; if (_state==1) goto K1; else if (_state==2) goto STAGEPOST1; bool bypassFinally = false; if (_state==1) goto K1; else if (_state==2) goto STAGEPOST1; bypassFinally = true; return; STAGEPOST1: if (_state==2) goto K2; bypassFinally = true; return; if (!bypassFinally) {... }
try { bool bypassFinally = false; if (_state==1) goto K1; else if (_state==2) goto STAGEPOST1; if (BeginAwait(...)) {bypassFinally = true; return;} K1: EndAwait() STAGEPOST1: try { if (_state==2) goto K2; if (BeginAwait(...)) {bypassFinally = true; return;} K2: EndAwait() } finally { if (!bypassFinally) {... } catch (Exception ex) { _builder.SetException(ex); } 2. Language [4/4]
framework “a language+framework push for compositional asynchrony” 1. Demo1. Demo /th/th 2. Language 3. Framework 3. Framework 4. Design 5. Theory
// network string s = await webClient.DownloadStringTaskAsync(" string s = await webClient.UploadStringTaskAsync(new Uri(" "dat"); await WebRequest.Create(" await socket.ConnectAsync("a.com",80); await workflowApplication.RunAsync(); await workflowApplication.PersistAsync(); PingReply r = await ping.SendTaskAsync("a.com"); // stream string s = await textReader.ReadToEndAsync(); await stream.WriteAsync(buffer, 0, 1024); await stream.CopyToAsync(stream2); // UI await pictureBox.LoadTaskAsync(" await soundPlayer.LoadTaskAsync(); // task/await, assuming “task” of type IEnumerable > T[] results = await TaskEx.WhenAll(tasks); Task winner = await TaskEx.WhenAny(tasks); Task task = TaskEx.Run(delegate {... return x;}); await TaskEx.Delay(100); await TaskEx.Yield(); await TaskScheduler.SwitchTo(); await Dispatcher.SwitchTo(); 3. Framework [1/9]: How to use the “Task Async Pattern” [TAP] We ultimately want the contents of TaskEx to be moved into Task.
3. Framework [2/9]: How to use TAP cancellation 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.
3. Framework [3/9]: How to use TAP cancellation [advanced] 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;
3. Framework [4/9]: How to use TAP 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, or any other class that implements IProgress... (it’s up to the consumer how to deal with progress) interface IProgress { void Report(T value); }
3. Framework [5/9]: How to use TAP progress [advanced] 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. Let us know if you’d like to see them or other variants moved into the framework.
3. Framework [6/9]: How to implement TAP cancellation/progress 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.
Task Delay(int ms, CancellationToken cancel); Task Run (Func function); Task > WhenAll (IEnumerable > tasks); Task > WhenAny (IEnumerable > tasks); // WhenAny is like Select. When you await it, you get the task that “won”. 3. Framework [7/9]: Task combinators // WhenAll over a LINQ query int[] results = await Task.WhenAll(from url in urls select GetIntAsync(url)); // WhenAny to implement a concurrent worker pool Queue todo =...; var workers = new HashSet >(); for (int i=0; i<10; i++) workers.Add(GetIntAsync(todo.Dequeue()); while (workers.Count>0) { var winner = await Task.WhenAny(workers); Console.WriteLine(await winner); workers.Remove(winner); if (todo.Count>0) workers.Add(GetIntAsync(todo.Dequeue()); }
Async Sub FireAndForgetAsync() Await t End Sub Async Function MerelySignalCompletionAsync() As Task Return End Function Async Function GiveResultAsync() As Task(Of Integer) Return 15 End Function 3. Framework [8/9]: Three kinds of async method FireAndForgetAsync() Await MerelySignalCompletionAsync() Dim 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.
“a language+framework push for compositional asynchrony” The “await” keyword makes asynchrony compositional with respect to all the other language constructs – something that callbacks and event-handlers can’t do. [language] The “Task ” type makes asynchrony compositional with respect to program architecture and libraries – something that EAP and APM can’t do. [framework]
// Task Asynchronous Pattern [TAP], with Cancellation and Progress Task GetStringAsync(Params..., [CancellationToken Cancel], [IProgress Progress]) // Asynchronous Programming Model [APM] IAsyncResult BeginGetString(Params..., AsyncCallback Callback, objec 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; } } 3. Framework [9/9]: Comparing TAP to its predecessors
design “a language+framework push for compositional asynchrony” 1. Demo1. Demo /th/th 2. Language 3. Framework 4. Design 4. Design 5. Theory
HotTask hot = GetIntAsync(); // task is already running (C#/VB) result = await hot; 4. Design [1/7]: hot vs cold Would like to make do with just a single Task type. The existing Task type is (mostly) hot. If we used the same type to represent hot+cold, “responsibility for starting the thing” is as onerous as non-GC “responsibility for deleting the thing”. Advanced combinators are easier with cold-factory. (But, with the language feature, we often don’t need them). Cold-factory feels weird if you sometimes need to f.Start() explicitly, but at other times you just “await f”. Cold and cold-factory let you set up events and other properties before running the object. ColdTask cold = FredAsync(); // task must be started manually HotTask hot = c.Start(); result = await hot; // or implicitly with “await cold” Factory f = FredAsync(); // a factory of hot-tasks (F#) HotTask hot1 = f.Start(); HotTask hot2 = f.Start(); // these are two different tasks result1 = await hot1; // usually implicit with “await f”
// async methods (C#/VB) Task FredAsync(int p) { await t; return “hello”; } 4. Design [2/7]: async blocks vs methods C# already has iterator methods. Value in staying consistent. Async blocks let you pass instance data to the Task you produce (e.g. “threadpool” above). Async methods can only be customized by the Task type that you produce. It felt hard for users to understand flow of control and concurrency in an async block. Does it start immediately? Run in parallel? Where are the copies of the parameters kept, and for how long? // async blocks (F#) Task FredAsync(int p) { return async(threadpool) { await t; return “hello”; }
’ use an explicit modifier to indicate an iterator/async method Async Function FredAsync() As Task(Of String) Iterator Function JonesAsync() As IEnumerable(Of String) 4. Design [3/7]: modifier vs implicit The modifier lets us avoid back-compat breaks while retaining the single word “await” (rather than multi-word contextual keywords) Modifier makes it easier for readers to see that the method is async Modifier shouldn’t appear in metadata because it’s only relevant for the implementation of the method: not at all for consumers C# doesn’t have a good place to put a modifier for lambdas... it looks ugly to write “Func f = async () => {...}”. Without the modifier, “await” is a breaking change to the language (if someone had an identifier or type of that name). Likewise “yield”. VB and C# both picked the modifier. // don’t use any modifier; if “await” is present then it’s async Function FredAsync() As Task(Of String) Await t // maybe instead “yield while” or “wait for” End Function
// (C#/VB) await in any // expression context await t; var x = await t; using (var x = await t); var y = (await t).Length; Console.WriteLine(await t); if (await t || await u) {...} while (await it.MoveNext()) {...} 4. Design [4/7]: statement await vs expression await It felt more VB-like and C#-like to allow await in arbitrary contexts However, some people say the order is confusing (in particular, it feels like a postfix operation “var x = t anon;”) Arbitrary expressions require STACK SPILLING... // (F#) await only in // statement contexts do! t; let! x = t; use! x = t;
// Allow void-returning async methods (C#/VB) void button1_Click() { var story = await GetDiggAsync(); textBox1.Text = story.Description; } 4. Design [5/7]: void-returning async methods Void-returning async methods are confusing, since the caller is unable to know when they have finished. They can only be used for “fire-and-forget” scenarios. But they make the common UI-event case much easier. Await “bubbles up”... if you have an await, then you must return Task, and so your caller must await you, and so it must return Task... all the way up the call stack, up to some “fire-and-forget” point which returns void, or up to some explicit thread creation.
// This task will end in a faulted state, but no one // will observe it: upon GC will it crash the program? var ft = Task.Run(delegate { throw new Exception(); }); return; 4. Design [6/7]: should unobserved faulted tasks crash the program? Normal exceptions cause a program-crash if uncaught The Task equivalent in.NET4 is that if a faulted Task is never observed (through doing “await” or WhenAll/WhenAny or ContinueWith or Wait or Result) then eventually (when it’s garbage-collected) it’ll crash This would make the second idiom too dangerous So we preferred to remove the “GC-crash-on-unobserved-faulted-tasks” behavior try { var ft1 = new var ft2 = new WebClient().DownloadTaskAsync("htq://a.com"); await ft1; // because of the exception here, await ft2; // ft2 won’t get observed: will it crash on GC? } catch (Exception) { }
interface IAsyncEnumerable { IAsyncEnumerator GetEnumerator(); } interface IAsyncEnumerator { Task MoveNext(); T Current {get;} } IAsyncEnumerable xx; foreach (await var x in xx) Console.WriteLine(x); // syntactic expansion of the “foreach await” loop: var ator = xx.GetEnumerator(); while (await ator.MoveNext()) { var x = ator.Current; Console.WriteLine(x); } 4. Design [7/7]: IAsyncEnumerable IAsyncEnumerable would be the.Net type for asynchronous structured streams – a stream is where the consumer can block (here by doing “await MoveNext”) and the producer can block (yield return). We’d also need to provide an entire new set of LINQ overloads which work with async. Entity Framework could work well with this interface. RX and IObservable would love the “foreach await”. They’ve already shipped a build with IAsyncEnumerable in it. Conclusion: wait and see. It might be premature for us to bite this off now. ??
theory “a language+framework push for compositional asynchrony” 1. Demo1. Demo /th/th 2. Language 3. Framework 4. Design 5. Theory 5. Theory
5. Theory [1/2]: CallCC This rewrite of “await t” feels a lot like a single-shot “t.CallCC(K1)”. I suppose “EndAwait()” is needed to propagate exceptions. But CallCC relies on reifying the stack -- which is impossible in.NET ! Await “infects” its way up the callstack. If you call await, then your return type will be Task, so your caller will have to await you, so his return type will be Task, and so on up to some top-level void-returning “fire and forget” method. Each async method re-ifies its local variables, after compiler transformation, storing them in the lambda’s closure class. So we have indeed re-ified the stack! albeit explicitly, thanks to the user explicitly making every function an async function. t.BeginAwait(K1); return; K1: t.EndAwait();
5. Theory [2/2]: Co-monads (Erik Meijer) T Extract (this Task src); Task Extend (this Task src, Func, T> selector); Task > Duplicate (this Task src); Task Select (this Task src, Func selector);
“a language+framework push for compositional asynchrony”