“a language+framework push for compositional asynchrony” Async.NET Working Group, F#, Axum, Task<T> Avner Aharoni, Mads Torgersen, Stephen Toub, Alex Turner, Lucian Wischik “a language+framework push for compositional asynchrony” Asynchrony Concurrency is about results that are delayed, and yielding control while awaiting them (co-operative multitasking). is about running or appearing to run two things at once, through cooperative or pre-emptive multitasking or multicore. Good reasons to use asynchrony: Good reasons to use concurrency: • 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. • 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 Concurrency is about results that are delayed, and yielding control while awaiting them (co-operative multitasking). is about running or appearing to run two things at once, through cooperative or pre-emptive multitasking or multicore. Good reasons to use asynchrony: Good reasons to use concurrency: • 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. • 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.” “a language+framework push for compositional asynchrony” Outline Of Talk 1. Demo 2. Language 3. Framework 4. Design 5. Theory of simple end-user experience with CTP. Threading story. specification of “await” keyword through compiler rewrites. the new “Task Asynchronous Pattern”; combinators; ecosystem hot vs cold; async blocks vs methods; IAsyncEnumerable<T> the academic theory around async – callcc? co-monads? 1. Demo /th 2. Language 3. Framework 4. Design 5. Theory “a language+framework push for compositional asynchrony” demo 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 [1/10] This is the original WPF code. 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("http://api.digg.com/?search&count=1&query=news")) Dim story = XElement.Parse(rss).<story> Return New DiggStory With { .Description=story.<description>.Value, .Id=story.@id.Value } End Function Function GetDiggComment() As String Dim web As New WebClient Dim rss = web.DownloadString(new Uri("http://api.digg.com/?getComments&count=1&id=" & id)) Return XElement.Parse(rss).<comment>.Value End Function Class DiggStory Public Id As String Public Description As String End Class [1/10] If you try to compile it on Silverlight, it doesn’t work. That’s because Silverlight lacks “DownloadString” API. Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click “DownloadString” is Try Dim story = GetDiggStory() synchronous – it blocks the UI, textBox1.Text = story.Description making it non-responsive. textBox2.Text = GetDiggComment(story.Id) Catch ex As Exception textBox1.Text = ex.ToString Silverlight chose not to have it, End Try End Sub because it’s bad to block the UI of the web-browser. Function GetDiggStory() As DiggStory Dim web As New WebClient Dim rss = web.DownloadString(New Uri("http://api.digg.com/?search&count=1&query=news")) Dim story = XElement.Parse(rss).<story> Return New DiggStory With { .Description=story.<description>.Value, .Id=story.@id.Value } End Function Function GetDiggComment() As String Dim web As New WebClient Dim rss = web.DownloadString(New Uri("http://api.digg.com/?getComments&count=1&id=" & id)) Return XElement.Parse(rss).<comment>.Value End Function Class DiggStory Public Id As String Public Description As String End Class “a language+framework push for compositional asynchrony” It was hard to make this code asynchronous before Async... [2/10] [2/10] Problem: DownloadString doesn’t exist Function GetDiggStory() As DiggStory Dim web As New WebClient Dim rss = web.DownloadString(New Uri("http://api.digg.com/?search&count=1&query=news")) Dim story = XElement.Parse(rss).<story> Return New DiggStory With { .Desc=story.<description>.Value, .Id=story.@id.Value } End Function Class DiggStory Public Id As String Public Description As String End Class [2/10] Solution: use DownloadStringAsync instead (which merely initiates the webrequest). And add an event-handler for when the server eventually comes back with a response. Function GetDiggStory() As DiggStory Dim web As New WebClient AddHandler web.DownloadStringCompleted, Sub(_, e) Dim rss = e.Result Dim story = XElement.Parse(rss).<story> Return New DiggStory With { .Desc=story.<description>.Value, .Id=story.@id.Value } End Sub web.DownloadStringAsync(New Uri("http://api.digg.com/?search&count=1&query=news")) End Function Class DiggStory Public Id As String Public Description As String End Class [3/10] Problem: now the “Return” statement doesn’t work. That’s because it now returns from the inner Sub, not the outer function. Function GetDiggStory() As DiggStory Dim web As New WebClient AddHandler web.DownloadStringCompleted, Sub(_, e) “Callbacks/events do not compose with the Return statement.” Dim rss = e.Result Dim story = XElement.Parse(rss).<story> Return New DiggStory With { .Desc=story.<description>.Value, .Id=story.@id.Value } End Sub web.DownloadStringAsync(New Uri("http://api.digg.com/?search&count=1&query=news")) End Function Class DiggStory Public Id As String Public Description As String End Class [3/10] Solution: deliver back the result of GetDiggStory through a callback of our own. Sub GetDiggStory(Callback As Action(Of DiggStory)) Dim web As New WebClient AddHandler web.DownloadStringCompleted, Sub(_, e) Dim rss = e.Result Dim story = XElement.Parse(rss).<story> Callback(New DiggStory With { .Desc=story.<description>.Value, .Id=story.@id.Value }) End Sub web.DownloadStringAsync(New Uri("http://api.digg.com/?search&count=1&query=news")) End Sub Class DiggStory Public Id As String Public Description As String End Class [4/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. Sub GetDiggStory(Callback As Action(Of DiggStory)) Dim web As New WebClient AddHandler web.DownloadStringCompleted, Sub(_, e) ... e.Error ... Dim rss = e.Result Dim story = XElement.Parse(rss).<story> Callback(New DiggStory With { .Desc=story.<description>.Value, .Id=story.@id.Value }) End Sub web.DownloadStringAsync(New Uri("http://api.digg.com/?search&count=1&query=news")) End Sub Class DiggStory Public Id As String Public Description As String End Class [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.” Sub GetDiggStory(Callback As Action(Of DiggStory)) Dim web As New WebClient AddHandler web.DownloadStringCompleted, Sub(_, e) If e.Error IsNot Nothing Then Callback(New DiggStory With { .Error=e.Error }) : Return Dim rss = e.Result Dim story = XElement.Parse(rss).<story> Callback(New DiggStory With { .Desc=story.<description>.Value, .Id=story.@id.Value }) End Sub web.DownloadStringAsync(New Uri("http://api.digg.com/?search&count=1&query=news")) End Sub Class DiggStory Public Id As String Public Description As String Public Error As Exception End Class [5/10] Problem: now we have to update Button1_Click, since it invokes functions that now take callbacks. Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click Try Dim story = GetDiggStory() textBox1.Text = story.Description textBox2.Text = GetDiggComments(story.Id) Catch ex As Exception textBox1.Text = ex.ToString End Try End Sub [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. Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Try GetDiggStory( Sub(story) textBox1.Text = story.Description GetDiggComment(story.Id, Sub(comment) textBox2.Text = comment End Sub) End Sub) Catch ex As Exception textBox1.Text = ex.ToString End Try End Sub “Callbacks/events do not compose with the semicolon Button1.Click operator.” [6/10] Problem: we have to fix up errorhandling as well, since errors might come either through an exception or through the callback. Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Try GetDiggStory( Sub(story) ... story.Error ... textBox1.Text = story.Description GetDiggComment(story.Id, Sub(comment) ... ?error? ... textBox2.Text = comment End Sub) End Sub) Catch ex As Exception textBox1.Text = ex.ToString End Try End Sub “Callbacks/events do not compose with exception Button1.Click 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. Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click Try GetDiggStory( Sub(story) If story.Error IsNot Nothing Then textBox1.Text = story.Error.ToString : Return textBox1.Text = story.Description GetDiggComment(story.Id, Sub(comment) ... ?maybe return a structure instead of just a string? ... textBox2.Text = comment End Sub) End Sub) Catch ex As Exception textBox1.Text = ex.ToString End Try End Sub “a language+framework push for compositional asynchrony” The Async CTP offers a better way to make this code asynchronous. [7/10] Problem: these methods need to be made asynchronous. 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("http://api.digg.com/?search&count=1&query=news")) Dim story = XElement.Parse(rss).<story> Return New DiggStory With { .Description=story.<description>.Value, .Id=story.@id.Value } End Function Function GetDiggComment() As String Dim web As New WebClient Dim rss = web.DownloadString(New Uri("http://api.digg.com/?getComments&count=1&id=" & id)) Return XElement.Parse(rss).<comment>.Value End Function Class DiggStory Public Id As String Public Description As String End Class [8/10] Solution part 1: Mark these methods as “Async” and change their return types to Task(Of ...). By convention, all async Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click methods in the framework Try Dim story = GetDiggStory() names ending in “Async”. textBox1.Text = story.Description textBox2.Text = GetDiggComment(story.Id) Catch ex As Exception textBox1.Text = ex.ToString End Try End Sub Async Function GetDiggStoryAsync() As Task(Of DiggStory) Dim web As New WebClient Dim rss = web.DownloadString(New Uri("http://api.digg.com/?search&count=1&query=news")) Dim story = XElement.Parse(rss).<story> Return New DiggStory With { .Description=story.<description>.Value, .Id=story.@id.Value } End Function Async Function GetDiggCommentAsync() As Task(Of String) Dim web As New WebClient Dim rss = web.DownloadString(New Uri("http://api.digg.com/?getComments&count=1&id=" & id)) Return XElement.Parse(rss).<comment>.Value End Function Class DiggStory Public Id As String Public Description As String End Class have [9/10] Solution part 2: “Await” the TaskAsynchronous versions of all calls, instead of invoking the synchronous versions. Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click Try Dim story = Await GetDiggStoryAsync() textBox1.Text = story.Description textBox2.Text = Await GetDiggCommentAsync(story.Id) Catch ex As Exception textBox1.Text = ex.ToString End Try End Sub Async Function GetDiggStoryAsync() As Task(Of DiggStory) Dim web As New WebClient Dim rss = Await web.DownloadStringTaskAsync(New Uri("http://api.digg.com/?search&count=1&query=news")) Dim story = XElement.Parse(rss).<story> Return New DiggStory With { .Description=story.<description>.Value, .Id=story.@id.Value } End Function Async Function GetDiggCommentAsync() As Task(Of String) Dim web As New WebClient Dim rss = Await web.DownloadStringTaskAsync(New Uri("http://api.digg.com/?getComments&count=1&id=" & id)) Return XElement.Parse(rss).<comment>.Value End Function Class DiggStory Public Id As String Public Description As String End Class [10/10] Solution part 3: Because Button1_Click has an “Await” in it, it too must be marked Async. (The Async modifier has to call hierarchy.) Async Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click “bubble-up” the Try Dim story = Await GetDiggStoryAsync() textBox1.Text = story.Description textBox2.Text = Await GetDiggCommentAsync(story.Id) Catch ex As Exception textBox1.Text = ex.ToString End Try End Sub Async Function GetDiggStoryAsync() As Task(Of DiggStory) Dim web As New WebClient Dim rss = Await web.DownloadStringTaskAsync(New Uri("http://api.digg.com/?search&count=1&query=news")) Dim story = XElement.Parse(rss).<story> Return New DiggStory With { .Description=story.<description>.Value, .Id=story.@id.Value } End Function Async Function GetDiggCommentAsync() As Task(Of String) Dim web As New WebClient Dim rss = Await web.DownloadStringTaskAsync(New Uri("http://api.digg.com/?getComments&c=1&id=" & id)) Return XElement.Parse(rss).<comment>.Value End Function Class DiggStory Public Id As String Public Description As String End Class My task: port it to Silverlight Status: finished ahead of schedule! I think I’ll go home early today. UI thread How the demo actually worked IOCP thread async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); var rss = await downTask; var digg = await diggTask; var digg = XElement.Parse(rss).<story>.<description>; return digg; } textBox1.Text = digg; } 1. Demo /th 2. Language 3. Framework 4. Design 5. Theory UI thread Click [1/12] A button-click arrives on the UI queue async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); var rss = await downTask; var digg = await diggTask; var digg = XElement.Parse(rss).<story>.<description>; return digg; } textBox1.Text = digg; } IOCP thread UI thread Click [2/12] Invoke some functions; get back “downTask” from the API async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); var rss = await downTask; downTask var digg = await diggTask; var digg = XElement.Parse(rss).<story>.<description>; return digg; } textBox1.Text = digg; } IOCP thread UI thread Click [3/12] “await downTask” assigns a continuation and returns diggTask async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); diggTask var rss = await downTask; downTask » ui.Post{Κ1} var digg = await diggTask; Κ1: var digg = XElement.Parse(rss).<story>.<description>; return digg; } textBox1.Text = digg; } IOCP thread UI thread Click [4/12] “await diggTask” assigns a continuation and returns async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); diggTask » ui.Post{Κ2} var rss = await downTask; downTask » ui.Post{Κ1} var digg = await diggTask; Κ1: var digg = XElement.Parse(rss).<story>.<description>; return digg; } Κ2: textBox1.Text = digg; } IOCP thread UI thread Click [5/12] Network packet arrives with data IOCP thread async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); diggTask » ui.Post{Κ2} var rss = await downTask; downTask » ui.Post{Κ1} var digg = await diggTask; rss Κ1: var digg = XElement.Parse(rss).<story>.<description>; return digg; } Κ2: textBox1.Text = digg; } UI thread Click [6/12] Invoke downTask’s continuation with that data IOCP thread async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); diggTask » ui.Post{Κ2} var rss = await downTask; downTask » ui.Post{Κ1} var digg = await diggTask; ui.Post{Κ1(rss)} Κ1: var digg = XElement.Parse(rss).<story>.<description>; return digg; } Κ2: textBox1.Text = digg; } rss UI thread Click [7/12] Continuation is a “Post”, i.e. addition to the UI queue IOCP thread async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); diggTask » ui.Post{Κ2} var rss = await downTask; var digg = await diggTask; ui.Post{Κ1(rss)} Κ1: var digg = XElement.Parse(rss).<story>.<description>; K1(rss) return digg; } Κ2: textBox1.Text = digg; } rss UI thread Click [8/12] UI thread executes K1, giving a result to the “await” IOCP thread async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); diggTask » ui.Post{Κ2} var rss = await downTask; var digg = await diggTask; ui.Post{Κ1(rss)} Κ1: var digg = XElement.Parse(rss).<story>.<description>; K1(rss) return digg; } Κ2: textBox1.Text = digg; } rss UI thread Click [9/12] “Return story” will signal completion of task IOCP thread async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); diggTask » ui.Post{Κ2} var rss = await downTask; var digg = await diggTask; ui.Post{Κ1(rss)} Κ1: var digg = XElement.Parse(rss).<story>.<description>; K1(rss) return digg; } Κ2: textBox1.Text = digg; } rss UI thread Click [10/12] Invoke diggTask’s continuation with data (by posting to UI queue) IOCP thread async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); diggTask » ui.Post{Κ2} var rss = await downTask; var digg = await diggTask; ui.Post{Κ1(rss)} Κ1: var digg = XElement.Parse(rss).<story>.<description>; K1(rss) return digg; } K2(story) Κ2: textBox1.Text = digg; } ui.Post(Κ2(story)) rss UI thread Click [11/12] Return from handling the K1 continuation IOCP thread async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); var rss = await downTask; var digg = await diggTask; ui.Post{Κ1(rss)} Κ1: var digg = XElement.Parse(rss).<story>.<description>; K1(rss) return digg; } K2(story) Κ2: textBox1.Text = digg; } ui.Post(Κ2(story)) rss UI thread Click [12/12] UI thread executes K2, giving a result to the “await” IOCP thread async void button1_Click() { var diggTask = GetDiggAsync(); async Task<string> GetDiggAsync() { var web = new WebClient(); var downTask = web.DownTaskAsync("http://digg.com"); var rss = await downTask; var digg = await diggTask; ui.Post{Κ1(rss)} Κ1: var digg = XElement.Parse(rss).<story>.<description>; K1(rss) return digg; } K2(story) Κ2: textBox1.Text = digg; } ui.Post(Κ2(story)) 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. “a language+framework push for compositional asynchrony” language 1. Demo /th 2. Language 3. Framework 4. Design 5. Theory 2. Language feature, explained as a syntactic rewrite async Task<string> GetDiggAsync(int p) { Over the following slides we’ll see how the compiler rewrites this async method… var x = await t; return x; } 2. Language [1/4] async Task<string> GetDiggAsync(int p) { TaskAwaiter<string> _temp; var x = await t; return x; } _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. 2. Language [1/4] async Task<string> GetDiggAsync(int p) { TaskAwaiter<string> _temp; var x = await t; return x; _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. } 2. Language [1/4] async Task<string> GetDiggAsync(int p) { TaskAwaiter<string> _temp; _temp = t.GetAwaiter(); if (_temp.BeginAwait(K1)) return; K1: var x = _temp.EndAwait(); return x; } 2. Language [2/4] async Task<string> GetDiggAsync(int p) { var _builder = TaskAwaiter<string> _temp; AsyncMethodBuilder<string>.Create(); int _state = 0; Action _moveNext = delegate { ... System.Runtime.CompilerServices.AsyncMethodBuilder<T> _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<string> GetDiggAsync(int p) { var _builder = AsyncMethodBuilder<string>.Create(); TaskAwaiter<string> _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] async Task<string> GetDiggAsync(int p) { var _builder = AsyncMethodBuilder<string>.Create(); TaskAwaiter<string> _temp; int _state = 0; Action _moveNext = delegate { try { //... jump table based on _state _state = 1; _temp = t.GetAwaiter(); if (_temp.BeginAwait(K1)) return; K1: var x = _temp.EndAwait(); _moveNext _state = 0; return x; _builder.SetResult(r); return; catch (Exception ex) { _builder.SetException(ex); } } _moveNext(); return _builder.Task; } 2. Language [3/4] async Task<string> GetDiggAsync(int p) { var _builder = AsyncMethodBuilder<string>.Create(); TaskAwaiter<string> _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; } “a language+framework push for compositional asynchrony” 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. 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<T>.GetAwaiter(): they are not baked into the language. 2. EXAMPLE: add “awaitability” to a Button’s click event Async Sub Form1_Load() Handles MyBase.Load Await Button1 MessageBox.Show("hello world") End Sub <Extension()> Function GetAwaiter(this As Button) As ButtonClickAwaiter Return New ButtonClickAwaiter With {.button = this} End Function Class ButtonClickAwaiter Friend button As Button Function BeginAwait(continuation As Action) As Boolean Dim lambda As EventHandler = Sub(sender As Object, e As EventArgs) RemoveHandler button.Click, lambda continuation() End Sub AddHandler button.Click, lambda Return True End Function Sub EndAwait() End Sub End Classs 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 bool bypassFinally = false; based _state ifon(_state==1) goto K1; else if (_state==2) goto STAGEPOST1; if (BeginAwait(...)) {return;} K1: EndAwait() try { bypassFinally = true; return; STAGEPOST1: if (_state==2) goto K2; if (BeginAwait(...)) {return;} K2: EndAwait() } finally { ... } bypassFinally = true; return; if (!bypassFinally) { ... } } catch (Exception ex) { _builder.SetException(ex); } 2. Language [4/4] 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); } “a language+framework push for compositional asynchrony” framework 1. Demo /th 2. Language 3. Framework 4. Design 5. Theory 3. Framework [1/9]: How to use the “Task Async Pattern” [TAP] // network string 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"); // stream string s = await textReader.ReadToEndAsync(); await stream.WriteAsync(buffer, 0, 1024); await stream.CopyToAsync(stream2); // UI await 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(); We ultimately want the contents of TaskEx to be moved into Task. 3. Framework [2/9]: How to use TAP cancellation 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. 3. Framework [3/9]: How to use TAP cancellation [advanced] 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; 3. Framework [4/9]: How to use TAP progress 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); } interface IProgress<T> { void Report(T value); } 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) 3. Framework [5/9]: How to use TAP progress [advanced] // 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. 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 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. 3. Framework [7/9]: Task<T> combinators 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 query int[] results = await Task.WhenAll(from url in urls select GetIntAsync(url)); // WhenAny to implement a concurrent worker pool Queue<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 Task.WhenAny(workers); Console.WriteLine(await winner); workers.Remove(winner); if (todo.Count>0) workers.Add(GetIntAsync(todo.Dequeue()); } 3. Framework [8/9]: Three kinds of async method 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 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<T>” type makes asynchrony compositional with respect to program architecture and libraries – something that EAP and APM can’t do. [framework] 3. Framework [9/9]: Comparing TAP to its predecessors // Task Asynchronous Pattern [TAP], with Cancellation and Progress Task<TR> GetStringAsync(Params..., [CancellationToken Cancel], [IProgress<TP> 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; } } “a language+framework push for compositional asynchrony” design 1. Demo /th 2. Language 3. Framework 4. Design 5. Theory 4. Design [1/7]: hot vs cold HotTask hot = GetIntAsync(); // task is already running (C#/VB) result = await hot; ColdTask cold = FredAsync(); // task must be started manually HotTask hot = c.Start(); result = await hot; // or implicitly with “await cold” Factory HotTask HotTask result1 • • • • • f = FredAsync(); hot1 = f.Start(); hot2 = f.Start(); = await hot1; // a factory of hot-tasks (F#) // these are two different tasks // usually implicit with “await f” 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. 4. Design [2/7]: async blocks vs methods // async blocks (F#) Task<string> FredAsync(int p) { return async(threadpool) { await t; return “hello”; } } • • • // async methods (C#/VB) Task<string> FredAsync(int p) { await t; return “hello”; } 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? 4. Design [3/7]: modifier vs implicit ’ use an explicit modifier to indicate an iterator/async method Async Function FredAsync() As Task(Of String) Iterator Function JonesAsync() As IEnumerable(Of String) // 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 • • • • • • 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<Task> 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. 4. Design [4/7]: statement await vs expression await // (F#) await only in // statement contexts do! t; let! x = t; use! x = t; • • • • // (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()) {...} 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... 4. Design [5/7]: void-returning async methods // Allow void-returning async methods (C#/VB) void button1_Click() { var story = await GetDiggAsync(); textBox1.Text = story.Description; } • • • 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. 4. Design [6/7]: should unobserved faulted tasks crash the program? // 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; try { var ft1 = new var ft2 = new await ft1; // await ft2; // WebClient().DownloadTaskAsync("!!@H&**"); WebClient().DownloadTaskAsync("htq://a.com"); because of the exception here, ft2 won’t get observed: will it crash on GC? } catch (Exception) { } • • • • 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 4. Design [7/7]: IAsyncEnumerable<T> interface IAsyncEnumerable<T> { IAsyncEnumerator<T> GetEnumerator(); } interface IAsyncEnumerator<T> { Task<bool> MoveNext(); T Current {get;} } ? IAsyncEnumerable<int> 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); } • • • • • 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<T> 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. “a language+framework push for compositional asynchrony” theory 1. Demo /th 2. Language 3. Framework 4. Design 5. Theory 5. Theory [1/2]: CallCC t.BeginAwait(K1); return; K1: t.EndAwait(); 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. 5. Theory [2/2]: Co-monads (Erik Meijer) T Task<T> Task<Task<S>> Task<T> Extract<T>(this Task<T> src); Extend<S,T>(this Task<S> src, Func<Task<S>, T> selector); Duplicate<S>(this Task<S> src); Select<S,T>(this Task<S> src, Func<S, T> selector); “a language+framework push for compositional asynchrony”