Surviving in an Async-First Development World
-
Upload
mirco-vanini -
Category
Software
-
view
102 -
download
2
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 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
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