Surviving in an Async-First Development World

42
www.xedotnet.org Surviving in an Async-First Development World Mirco Vanini Microsoft® MVP Windows Development AllSeen Alliance - AllJoyn® Ambassador OCF® Members

Transcript of Surviving in an Async-First Development World

Page 1: Surviving in an Async-First Development World

www.xedotnet.org

Surviving in an Async-First Development World

Mirco Vanini Microsoft® MVP Windows Development AllSeen Alliance - AllJoyn® Ambassador OCF® Members

Page 2: Surviving in an Async-First Development World

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

Page 3: Surviving in an Async-First Development World

24/03/17 3

Genesisusing (var sr = new StreamReader("text.txt")){    var content = sr.ReadToEnd();}

Page 4: Surviving in an Async-First Development World

24/03/17 3

Genesisusing (var sr = new StreamReader("text.txt")){    var content = sr.ReadToEnd();}

Thread wkThread = new Thread(new ThreadStart(ReadFileText));wkThread.Start();

Page 5: Surviving in an Async-First Development World

24/03/17 3

Genesisusing (var sr = new StreamReader("text.txt")){    var content = sr.ReadToEnd();}

Thread wkThread = new Thread(new ThreadStart(ReadFileText));wkThread.Start();

private delegate void NoParamDelegate(); NoParamDelegate npd = new NoParamDelegate(ReadFileText);npd.BeginInvoke(new AsyncCallback(CallBack), null);

Page 6: Surviving in an Async-First Development World

24/03/17 3

Genesisusing (var sr = new StreamReader("text.txt")){    var content = sr.ReadToEnd();}

Thread wkThread = new Thread(new ThreadStart(ReadFileText));wkThread.Start();

private delegate void NoParamDelegate(); NoParamDelegate npd = new NoParamDelegate(ReadFileText);npd.BeginInvoke(new AsyncCallback(CallBack), null);

Task.Factory.StartNew(() =>{    using (StreamReader sr = new StreamReader("text.txt"))    {        return sr.ReadToEnd();    }}).ContinueWith((t) => t.Result         // UI update);

Page 7: Surviving in an Async-First Development World

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.

Page 8: Surviving in an Async-First Development World

24/03/17 5

What Happens in an Async Method

Page 9: Surviving in an Async-First Development World

24/03/17 6

Question: How it works?Task delayTask = Task.Delay(TimeSpan.FromSecond(2));var httpClient = new HttpClient();Task<string> downlaodTask = httpClient.GetStringAsync(http://www.example.com);

Page 10: Surviving in an Async-First Development World

24/03/17 7

Understanding the Costs of Async and Await public static async Task SimpleBodyAsync() { Console.WriteLine("Hello, Async World!");}

Page 11: Surviving in an Async-First Development World

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__IStateMachine {  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();  }   ...}

Page 12: Surviving in an Async-First Development World

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).

Page 13: Surviving in an Async-First Development World

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.

Page 14: Surviving in an Async-First Development World

24/03/17 10

Benefit of Asynchrony • Free up threads • This is the opposite of "creating more threads

" or "using a thread pool thread "

Page 15: Surviving in an Async-First Development World

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

Page 16: Surviving in an Async-First Development World

Demo

18/11/16 12

Step 1 Step 2

Page 17: Surviving in an Async-First Development World

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()”

Page 18: Surviving in an Async-First Development World

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

Page 19: Surviving in an Async-First Development World

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

Page 20: Surviving in an Async-First Development World

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

Page 21: Surviving in an Async-First Development World

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

Page 22: Surviving in an Async-First Development World

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

Page 23: Surviving in an Async-First Development World

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

Page 24: Surviving in an Async-First Development World

24/03/17 15

Avoid Async Void // A. Use a taskTask<BitmapImage> m_bmpTask;

protected override async void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); await PlayIntroSoundAsync(); var bmp = await m_bmpTask; image1.Source = bmp; Canvas.SetLeft(image1, Window.Current.Bounds.Width - bmp.PixelWidth); } protected override void LoadState(Object nav, Dictionary<String, Object> pageState) { m_bmpTask = LoadBitmapAsync(); } private async Task<BitmapImage> LoadBitmapAsync() { var bmp = new BitmapImage(); ... return bmp; }

Page 25: Surviving in an Async-First Development World

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;}

Page 26: Surviving in an Async-First Development World

24/03/17 16

Threadpool work1

request in

response out

500ms

work2

work3

work4

work5

Page 27: Surviving in an Async-First Development World

24/03/17 17

Threadpool

// table1.DataSource = LoadHousesInParallel(1,5);// table1.DataBind();

public List<House> LoadHousesInParallel(int first, int last){ var loadedHouses = new BlockingCollection<House>();

Parallel.For(first, last+1, i => { House house = House.Deserialize(i); loadedHouses.Add(house); });

return loadedHouses.ToList();}

Page 28: Surviving in an Async-First Development World

24/03/17 17

Threadpool

1 2

3

4

5

Parallel.Forrequest in

Page 29: Surviving in an Async-First Development World

24/03/17 17

Threadpool

response out 300ms

work1 work2

work3 work4

work5

Parallel.Forrequest in

Page 30: Surviving in an Async-First Development World

24/03/17 17

Threadpool

response out 300ms

work1 work2

work3 work4

work5

Parallel.Forrequest in

Is it CPU-bound,

or I/O-bound?

Page 31: Surviving in an Async-First Development World

24/03/17 17

Threadpool

response out 300ms

work1 work2

work3 work4

work5

Parallel.Forrequest in

Page 32: Surviving in an Async-First Development World

24/03/17 18

Threadpool // table1.DataSource = await LoadHousesAsync(1,5);// table1.DataBind();

public async Task<List<House>> LoadHousesAsync(int first, int last){ var tasks = new List<Task<House>>();

for (int i = first; i <= last; i++) { Task<House> t = House.LoadFromDatabaseAsync(i); tasks.Add(t); }

House[] loadedHouses = await Task.WhenAll(tasks); return loadedHouses.ToList();}

Page 33: Surviving in an Async-First Development World

24/03/17 18

Threadpool start1

request in

start2start3start4start5

Page 34: Surviving in an Async-First Development World

24/03/17 18

Threadpool

end2

start1

request in

start2start3start4start5

response out ~100ms

end5

end1end3end4

Page 35: Surviving in an Async-First Development World

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.

Page 36: Surviving in an Async-First Development World

24/03/17 20

Reactive Multithreaded

Asynchronous Parallel

Concurrent

Concurrent mode

Page 37: Surviving in an Async-First Development World

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

Page 38: Surviving in an Async-First Development World

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. }

Page 39: Surviving in an Async-First Development World

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)  {    ...  }}

Page 40: Surviving in an Async-First Development World

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.

Page 42: Surviving in an Async-First Development World

24/03/17 24

Who I am

www.adamfactory.com [email protected] @MircoVanini

Mirco Vanini Microsoft® MVP Windows Development AllSeen Alliance - AllJoyn® Ambassador OCF® Member

TinyCLR.it