1. www.xedotnet.org
Surviving in an Async-First
Development World
Mirco Vanini
Microsoft® MVP Windows Development
AllSeen Alliance - AllJoyn® Ambassador
OCF® Members
2. Genesis
Keywords
Costs of Async and Await
Understanding the Keywords
Context
Benefit of Asynchrony
Avoid async void
Threadpool
Configure context
24/03/17 2
Agenda
7. 24/03/17 4
Introducing the Keywords
public async Task DoSomethingAsync()
{
// In the Real World, we would actually do something...
// For this example, we're just going to (asynchronously)
// wait 100ms.
//
await Task.Delay(100);
}
The async and await keywords in C# are the heart of async programming.
By using those two keywords, you can use resources in the .NET Framework or the
Windows
Runtime to create an asynchronous method almost as easily as you create a
synchronous
method. Asynchronous methods that you define by using async and await are
referred
to as async methods.
9. 24/03/17 6
Question: How it works?
Task delayTask = Task.Delay(TimeSpan.FromSecond(2));
var httpClient = new HttpClient();
Task<string> downlaodTask = httpClient.GetStringAsync(htt
p://www.example.com);
10. 24/03/17 7
Understanding the Costs of Async and
Await
public static async Task SimpleBodyAsync()
{
Console.WriteLine("Hello, Async World!");
}
11. 24/03/17 7
Understanding the Costs of Async and
Await
public static async Task SimpleBodyAsync()
{
Console.WriteLine("Hello, Async World!");
}
[DebuggerStepThrough]
public static Task SimpleBodyAsync() {
<SimpleBodyAsync>d__0 d__ = new <SimpleBodyAsync>d__0
();
d__.<>t__builder = AsyncTaskMethodBuilder.Create();
d__.MoveNext();
return d__.<>t__builder.Task;
}
[CompilerGenerated]
[StructLayout(LayoutKind.Sequential)]
private struct <SimpleBodyAsync>d__0 : <>t__IStateMachi
ne {
private int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public Action <>t__MoveNextDelegate;
public void MoveNext() {
try {
if (this.<>1__state == -1) return;
Console.WriteLine("Hello, Async World!");
}
catch (Exception e) {
this.<>1__state = -1;
this.<>t__builder.SetException(e);
return;
}
this.<>1__state = -1;
this.<>t__builder.SetResult();
}
...
}
12. 24/03/17 8
Understanding the Keywords
• The “async” keyword enables the “await” keyword in
that method and changes how method results are
handled. That’s all the async keyword does! It does not
run this method on a thread pool thread, or do any
other kind of magic. The async keyword only enables
the await keyword (and manages the method results).
• The “await” keyword is where things can get
asynchronous. Await is like a unary operator: it takes a
single argument, an awaitable (an “awaitable” is an
asynchronous operation). Await examines that awaitable
to see if it has already completed; if the awaitable has
already completed, then the method just continues
running (synchronously, just like a regular method).
13. 24/03/17 9
Context
• When you await a built-in awaitable, then the
awaitable will capture the current “context” and
later apply it to the remainder of the async
method
• If you’re on a UI thread, then it’s a UI context.
• If you’re responding to an ASP.NET request, then it’s an
ASP.NET request context.
• Otherwise, it’s usually a thread pool context.
15. 24/03/17 11
Avoid Async Void
• There are three possible return types for async
methods: Task, Task<T> and void.
• Void-returning async methods have a specific
purpose: to make asynchronous event handlers
possible
• When converting from synchronous to
asynchronous code, any method returning a
type T becomes an async method returning
Task<T>, and any method returning void
becomes an async method returning Task
17. 24/03/17 13
Avoid Async Void
• Async void is a “fire-and-forget” mechanism...
• The caller is unable to know when an async void
has finished
• The caller is unable to catch exceptions thrown
from an async void
• Use async void methods only for top-level event
handlers (and their like)
• Use async Task-returning methods everywhere else
If you need fire-and-forget elsewhere, indicate it
explicitly e.g. “FredAsync().FireAndForget()”
18. 24/03/17 14
// Q. It sometimes shows PixelWidth and PixelHeight are both 0 ???
BitmapImage m_bmp;
protected override async void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
await PlayIntroSoundAsync();
image1.Source = m_bmp;
Canvas.SetLeft(image1, Window.Current.Bounds.Width - m_bmp.PixelWidth);
}
protected override async void LoadState(Object nav, Dictionary<String, Object>
pageState) {
m_bmp = new BitmapImage();
var file = await StorageFile.GetFileFromApplicationUriAsync("ms-appx:///
pic.png");
using (var stream = await file.OpenReadAsync()) {
await m_bmp.SetSourceAsync(stream);
}
}
Avoid Async Void
19. 24/03/17 14
// Q. It sometimes shows PixelWidth and PixelHeight are both 0 ???
BitmapImage m_bmp;
protected override async void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
await PlayIntroSoundAsync();
image1.Source = m_bmp;
Canvas.SetLeft(image1, Window.Current.Bounds.Width - m_bmp.PixelWidth);
}
protected override async void LoadState(Object nav, Dictionary<String, Object>
pageState) {
m_bmp = new BitmapImage();
var file = await StorageFile.GetFileFromApplicationUriAsync("ms-appx:///
pic.png");
using (var stream = await file.OpenReadAsync()) {
await m_bmp.SetSourceAsync(stream);
}
}
class LayoutAwarePage : Page
{
private string _pageKey;
protected override void OnNavigatedTo(NavigationEventArgs
e)
{
if (this._pageKey != null) return;
this._pageKey = "Page-" + this.Frame.BackStackDepth;
...
this.LoadState(e.Parameter, null);
}
}
Avoid Async Void
20. 24/03/17 14
// Q. It sometimes shows PixelWidth and PixelHeight are both 0 ???
BitmapImage m_bmp;
protected override async void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
await PlayIntroSoundAsync();
image1.Source = m_bmp;
Canvas.SetLeft(image1, Window.Current.Bounds.Width - m_bmp.PixelWidth);
}
protected override async void LoadState(Object nav, Dictionary<String, Object>
pageState) {
m_bmp = new BitmapImage();
var file = await StorageFile.GetFileFromApplicationUriAsync("ms-appx:///
pic.png");
using (var stream = await file.OpenReadAsync()) {
await m_bmp.SetSourceAsync(stream);
}
}
Avoid Async Void
21. 24/03/17 14
// Q. It sometimes shows PixelWidth and PixelHeight are both 0 ???
BitmapImage m_bmp;
protected override async void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
await PlayIntroSoundAsync();
image1.Source = m_bmp;
Canvas.SetLeft(image1, Window.Current.Bounds.Width - m_bmp.PixelWidth);
}
protected override async void LoadState(Object nav, Dictionary<String, Object>
pageState) {
m_bmp = new BitmapImage();
var file = await StorageFile.GetFileFromApplicationUriAsync("ms-appx:///
pic.png");
using (var stream = await file.OpenReadAsync()) {
await m_bmp.SetSourceAsync(stream);
}
}
Avoid Async Void
22. 24/03/17 14
// Q. It sometimes shows PixelWidth and PixelHeight are both 0 ???
BitmapImage m_bmp;
protected override async void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
await PlayIntroSoundAsync();
image1.Source = m_bmp;
Canvas.SetLeft(image1, Window.Current.Bounds.Width - m_bmp.PixelWidth);
}
protected override async void LoadState(Object nav, Dictionary<String, Object>
pageState) {
m_bmp = new BitmapImage();
var file = await StorageFile.GetFileFromApplicationUriAsync("ms-appx:///
pic.png");
using (var stream = await file.OpenReadAsync()) {
await m_bmp.SetSourceAsync(stream);
}
}
Avoid Async Void
23. 24/03/17 14
// Q. It sometimes shows PixelWidth and PixelHeight are both 0 ???
BitmapImage m_bmp;
protected override async void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
await PlayIntroSoundAsync();
image1.Source = m_bmp;
Canvas.SetLeft(image1, Window.Current.Bounds.Width - m_bmp.PixelWidth);
}
protected override async void LoadState(Object nav, Dictionary<String, Object>
pageState) {
m_bmp = new BitmapImage();
var file = await StorageFile.GetFileFromApplicationUriAsync("ms-appx:///
pic.png");
using (var stream = await file.OpenReadAsync()) {
await m_bmp.SetSourceAsync(stream);
}
}
Avoid Async Void
25. 24/03/17 16
Threadpool
// table1.DataSource = LoadHousesSequentially(1,5);
// table1.DataBind();
public List<House> LoadHousesSequentially(int first, int
last)
{
var loadedHouses = new List<House>();
for (int i = first; i <= last; i++) {
House house = House.Deserialize(i);
loadedHouses.Add(house);
}
return loadedHouses;
}
35. 24/03/17 19
Threadpool
• CPU-bound work means things like: LINQ-over-
objects, or big iterations, or computational inner
loops.
• Parallel.ForEach and Task.Run are a good way to
put CPU-bound work onto the thread pool.
• Use of threads will never increase throughput on a
machine that’s under load.
• For IO-bound “work”, use await rather than
background threads.
• For CPU-bound work, consider using background
threads via Parallel.ForEach or Task.Run, unless
you're writing a library, or scalable server-side
code.
37. 24/03/17 21
Configure Context
• The “context” is captured by default when an
incomplete Task is awaited, and that this captured
context is used to resume the async method
• Most of the time, you don’t need to sync back to
the “main” context ! Most async methods will be
designed with composition in mind.
• Awaiter not capture the current context by
calling ConfigureAwait
• Avoids unnecessary thread marshaling
• Code shouldn’t block UI thread, but avoids deadlocks if
it does
38. 24/03/17 21
Configure Context
• The “context” is captured by default when an
incomplete Task is awaited, and that this captured
context is used to resume the async method
• Most of the time, you don’t need to sync back to
the “main” context ! Most async methods will be
designed with composition in mind.
• Awaiter not capture the current context by
calling ConfigureAwait
• Avoids unnecessary thread marshaling
• Code shouldn’t block UI thread, but avoids deadlocks if
it does
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
// Since we asynchronously wait, the UI thread is not blocked by the file
download.
//
await DownloadFileAsync(fileNameTextBox.Text);
// Since we resume on the UI context, we can directly access UI elements.
//
resultTextBox.Text = "File downloaded!";
}
private async Task DownloadFileAsync(string fileName)
{
// Use HttpClient or whatever to download the file contents.
//
var fileContents = await
DownloadFileContentsAsync(fileName).ConfigureAwait(false);
// Note that because of the ConfigureAwait(false), we are not on the
// original context here.
// Instead, we're running on the thread pool.
// Write the file contents out to a disk file.
//
await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false);
// The second call to ConfigureAwait(false) is not *required*, but it is Good
Practice.
39. 24/03/17 22
Async Console
• Unfortunately, that doesn’t work, the
compiler will reject an async Main method
• You can work around this by providing your
own async-compatible contex
class Program
{
static async void Main(string[] args)
{
...
}
}
40. 24/03/17 22
Async Console
• Unfortunately, that doesn’t work, the
compiler will reject an async Main method
• You can work around this by providing your
own async-compatible contex
class Program
{
static async void Main(string[] args)
{
...
}
}
class Program
{
static int Main(string[] args)
{
try
{
return AsyncContext.Run(() => MainAsync(args));
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
return -1;
}
}
static async Task<int> MainAsync(string[] args)
{}
}
Console applications and Win32 services do not have a
suitable context,
and AsyncContext or AsyncContextThread could be used in
those situations.
41. 24/03/17 23
Links
• Async and Await
• Async/Await - Best Practices in Asynchronous
Programming
• Async Performance: Understanding the Costs of
Async and Await
• Async programming deep dive
42. 24/03/17 24
Who I am
www.adamfactory.com
mirco.vanini@adamfactory.com
@MircoVanini
Mirco Vanini
Microsoft® MVP Windows Development AllSeen
Alliance - AllJoyn® Ambassador
OCF® Member
TinyCLR.it