async-await best practices in 10 minutes
TRANSCRIPT
50ª Reunião Presencial @ LISBOADateTime.Parse(“22-11-2014", new CultureInfo("pt-PT"));
http://netponto.org
hashtag #netponto
Async-Await Best Practincesin 10 minutes
Paulo Morgado
http://netponto.org50ª Reunião Lisboa – 22-11-2014
Paulo Morgado• http://PauloMorgado.NET/• @PauloMorgado• http://about.me/PauloMorgado• http://www.slideshare.net/PauloJorgeMorgado• http://pontonetpt.org/blogs/paulomorgado/• http://blogs.msmvps.com/paulomorgado/• http://weblogs.asp.net/paulomorgado• http://www.revista-programar.info/author/pmorgad
o/
For goodness’ sake,stop using async void!
Async void is only for event handlersPrinciplesAsync void is a “fire-and-forget” mechanism...The caller is unable to know when an async void has finishedThe caller is unable to catch exceptions thrown from an async void
(instead they get posted to the UI message-loop)
GuidanceUse async void methods only for top-level event handlers (and their like)Use async Task-returning methods everywhere elseIf you need fire-and-forget elsewhere, indicate it explicitly e.g. “FredAsync().FireAndForget()”When you see an async lambda, verify it
The problem is events.They’re not going away.
Async over events• Principles
Callback-based programming, as with events, is hard
• GuidanceIf the event-handlers are largely independent, then leave them as eventsBut if they look like a state-machine, then await is sometimes easierTo turn events into awaitable Tasks, use TaskCompletionSource
Is it CPU-bound,or I/O-bound?
ThreadpoolPrinciplesCPU-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.Thread pool will gradually feel out how many threads are needed to make best progress.Use of threads will never increase throughput on a machine that’s under load.
GuidanceFor 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.
Don’t lie
Two ways of thinking about asynchrony
• Foo();• Perform something here and now.• I’ll regain control to execute
something else when it’s done.
• var task = FooAsync();• Initiate something here and now.• I’ll regain control to execute
something else “immediately”.
From the method signature (how people call it)
Uses a CPU core solidly while it runs
void Foo(){ for (int i=0; i<100; i++) Math.Sin(i);}
From the method implementation (what resources it uses) Hardly touches the CPU
async Task FooAsync(){ await client.DownloadAsync();}
Async methods: Your caller’s assumptions“This method’s name ends with ‘Async’, so…”
“…calling it won’t spawn new threads in my server app”
“…I can parallelize by simply calling it multiple times”
Is this true for your async methods?
Libraries generally shouldn’t use Task.Run()
Your callers should be the ones to call Task.Run“await task;”Captures the current SyncContext before awaiting.When it resumes, uses SyncContext.Post() to resume “in the same place”(If SyncContext is null, uses the TaskScheduler)
For application-level code:This behavior is almost always what you want.
For library-level code:This behavior is rarely what you want!
Sync methods: Your caller’s assumptions“There’s a synchronous version of this method…”
“…I guess it must be faster than the async version”
“…I can call it from the UI thread if the latency’s fine”
void Foo() { FooAsync().Wait(); } -- will deadlock!!!
Library methods shouldn't liePrinciplesIn a server app, spinning up threads hurts scalabilty.The app (not the library) is in the best position to manage its own threads.Users will assume they know your method's implementation by looking at its signature.
GuidanceDefine an async signature “FooAsync” when your implementation is truly async.Define a sync signature "Foo" when your implementation is fast and won't deadlock.Don't use blocking calls to Wait() .Result in libraries; that invites deadlocks.
Use ConfigureAwait(false)
SynchronizationContextRepresents a target for work via its Post methodWindowsFormsSynchronizationContext
.Post() does Control.BeginInvoke
DispatcherSynchronizationContext.Post() does Dispatcher.BeginInvoke
AspNetSynchronizationContext.Post() ensures one-at-a-time
… // ~10 in .NET Framework, and you can write your own… // Is the core way for “await” to know how to put you back
SynchronizationContext and AwaitPrinciplesIn a server app, spinning up threads hurts scalabilty.The app (not the library) is in the best position to manage its own threads.Users will assume they know your method's implementation by looking at its signature.
GuidanceDefine an async signature “FooAsync” when your implementation is truly async.Define a sync signature "Foo" when your implementation is fast and won't deadlock.Don't use blocking calls to Wait() .Result in libraries; that invites deadlocks.
SynchronizationContext: ConfigureAwaitTask.ConfigureAwait(bool continueOnCapturedContext)await t.ConfigureAwait(true) // default
Post continuation back to the current context/scheduler
await t.ConfigureAwait(false)If possible, continue executing where awaited task completes
ImplicationsPerformance (avoids unnecessary thread marshaling)Deadlock (code shouldn’t block UI thread, but avoids deadlocks if it does)
Use ConfigureAwait(false)PrinciplesSynchronizationContext is captured before an await, and used to resume from await.In a library, this is an unnecessary perf hit.It can also lead to deadlocks if the user (incorrectly) calls Wait() on your returned Task..
GuidanceIn library methods, use "await t.ConfigureAwait(false);"
Await all the way
Library perf considerationsPrinciplesThe compiler provides, through the await keyword, sequential execution of the code.
GuidanceDon’t mix async-await with ContinuesWith.
Task.Run is the way to create new tasks
Task.RunPrinciplesTask.Run returns hot tasks (running or completed) created with settings suited to async-await.
GuidanceDon’t use Task.Factory.StartNew or the Task (or Task<T>) constructor.
Use the CancellationToken
Use the CancellationTokenPrinciplesThe CancellationToken structure is the way to signal and handle cancellation.
GuidanceIf you want your API to be cancellable, use cancellation tokens.If your code uses APIs that use cancellation tokens, use them.Always check the cancellation tokens.
Library perf considerations
Library perf considerationsPrinciplesAsync methods are faster than what you could write manually, but still slower than synchronous.The chief cost is in memory allocation (actually, in garbage collection).The "fast path" bypasses some allocations.
GuidanceAvoid designing "chatty" APIs where async methods are called in an inner loop; make them "chunky".If necessary, cache the returned Task object (even with cache size "1"), for zero allocations per call.As always, don't prematurely optimize!
Questões?
Resources• Talk: Async best practices
– http://blogs.msdn.com/b/lucian/archive/2013/11/23/talk-mvp-summit-async-best-practices.aspx
• Six Essential Tips For Async – Introduction– http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Three-Essential-Tips-
For-Async-Introduction
• Curah! async-await General– http://curah.microsoft.com/45553/asyncawait-general
• Curah! async-await and ASP.NET– http://curah.microsoft.com/44400/async-and-aspnet
Questões?
Patrocinadores “GOLD”
Twitter: @PTMicrosoft
http://www.microsoft.com/portugal
Twitter: @FindMoreC
http://www.findmore.eu
Patrocinadores “Bronze”
http://bit.ly/netponto-aval-50
* Para quem não puder preencher durante a reunião, iremos enviar um email com o link à tarde
Próximas reuniões presenciais22/11/2014 – Novembro – 50ª Reunião! (Lisboa)13/12/2014 – Dezembro (Lisboa)24/01/2015 – Janeiro (Lisboa)??/??/2015 – ????? (Porto)??/??/2015 – ????? (?????)
Reserva estes dias na agenda! :)