“a language+framework push for compositional asynchrony” Async.NET Working Group, F#, Axum, Task Avner Aharoni, Mads Torgersen, Stephen Toub, Alex Turner, Lucian Wischik.
Download
Report
Transcript “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”
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, [email protected] }
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, [email protected] }
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, [email protected] }
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, [email protected] }
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, [email protected] }
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, [email protected] })
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, [email protected] })
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, [email protected] })
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, [email protected] }
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, [email protected] }
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, [email protected] }
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, [email protected] }
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”