Surviving in an Async-First Development World

Post on 06-Apr-2017

102 views 2 download

Transcript of 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

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

24/03/17 3

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

24/03/17 3

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

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

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

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

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.

24/03/17 5

What Happens in an Async Method

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

24/03/17 7

Understanding the Costs of Async and Await public static async Task SimpleBodyAsync() { Console.WriteLine("Hello, Async 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();  }   ...}

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

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.

24/03/17 10

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

" or "using a thread pool thread "

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

Demo

18/11/16 12

Step 1 Step 2

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

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

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

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

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

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

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

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

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

24/03/17 16

Threadpool work1

request in

response out

500ms

work2

work3

work4

work5

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

24/03/17 17

Threadpool

1 2

3

4

5

Parallel.Forrequest in

24/03/17 17

Threadpool

response out 300ms

work1 work2

work3 work4

work5

Parallel.Forrequest in

24/03/17 17

Threadpool

response out 300ms

work1 work2

work3 work4

work5

Parallel.Forrequest in

Is it CPU-bound,

or I/O-bound?

24/03/17 17

Threadpool

response out 300ms

work1 work2

work3 work4

work5

Parallel.Forrequest in

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

24/03/17 18

Threadpool start1

request in

start2start3start4start5

24/03/17 18

Threadpool

end2

start1

request in

start2start3start4start5

response out ~100ms

end5

end1end3end4

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.

24/03/17 20

Reactive Multithreaded

Asynchronous Parallel

Concurrent

Concurrent mode

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

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

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

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.

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