How to (not) use
async ⚡️ await
10+ examples
The C# / .NET edition, not the JavaScript one...
SysKit HQ, Zagreb 2019-06-13
bit.ly/syskit-async-await
Vedran Mandić, MCSD, MCT
First,
thank you for having me,
SysKit you are awesome :)
Hi, I am Vedran. 👋
- I freelance, talk and lecture C# / .NET and JavaScript
- currently in manufacturing industry
- on medium and dev.to by @vekzdran
- on github by @vmandic
- in this since 2009, fresh!
- bad at basics, suck at CSS, moderate in C#... jk 🙈
Agenda
- Must-know 🌟 terminology
- async / await FAQ 10+ examples
- demo code 👩💻
- Q&A (please after 1 && 2) ⏰
- Share and apply, please, plz...
15min + 40min (very fast tempo)
(very boring) Important
Terminology Recap
You can take a sip of 🍺 now...
System.Threading.Thread
- handles CPU time to process (your app's) work
- has priority, (synchronization) context, state
- an OS thread is mapped to a managed .NET Thread
- different roles e.g. main, UI, worker, ThreadPool
- different types e.g. foreground and background
- new Thread() is not from a ThreadPool & has no SyncContext
- can run in Multiple/Parallel
- synced by many constructs such as lock() { ... }, monitors...
- TIP: favor ThreadPool threads to save memory and time
System.Threading.ThreadPool
- your .NET app (AppDomain) gets / has one
- "Worker" and "I/O Completion Port" threads
- reserve of (app process) worker threads
- has a min and max limit, can be changed, per CPU
- exceeding min limit throttles for 500ms (Redis anyone!?)
- careful not to exhaust and spike memory
- ThreadPool bg threads won't block the fg threads
- [ThreadStaticAttribute] values are not cleared on reuse
System.Threading.Tasks.Task
- since .NET v4.0, before async / await
- promise of work to be done (by a Thread)
- uses (by default) bg Threads from ThreadPool
- TIP: use LongRunning Tasks if action takes long
- can be awaited (in an async scope)
- Task<T> to return a T .Result (else void .Wait())
- can be nested / attached
- TIP: use .Unwrap() to resolve inner Task<Task<T>> results
SynchronizationContext
- represents a "location" where a Thread is run / executed
- WinFormsSynchronizationContext, AspNetSC, default...
- behaviour differs across .NET app type impl.
- designed to send data across threads
-
WinForms forbids updating UI from other contexts!
- await captures the current context and resumes on it
- ASP.NET Core has no SynchronizationContext:
-> perf is inherently better!
TaskScheduler
- app Tasks scheduling & execution
- Abstraction over SynchronizationContext
- can get current SyncContext
- TaskScheduler.FromCurrentSynchronizationContext();
- "queues Tasks onto Threads" MSDN
- TaskScheduler.Default is the default impl.
->ThreadPool global & local queues (parent vs child) - abstract but inheritable
- build your own, e.g. limit number of Threads in app
How is a task scheduled?
Deadlock
- one or more threads blocks / waits / hangs on a line of code indefinitely waiting for another thread, the application is blocked and UNUSABLE from that point
- e.g. WinForms (SynchronizationContext) misuse when updating the MainForm UI controls from non MainForm threads
- e.g. ASP.NET (non .NET Core) accessing .Result property or .Wait() method of non-awaited Task
(thread) Blocking
-
blocked thread waits / hangs and can not be used for other work execution, process memory usage spikes if you keep spawning them in this scenario, ThreadPool exhaustion is imminent, e.g. an endpoint calling .Result
- applying valid async / await syntax releases the awaiting thread back to the ThreadPool and respects SynchronizationContext implementations
PROTIP:
always use async / await, no need for overengineering
public async Task<string> GetUsername(int id)
{
var user = await db.Users.SingleOrDefaultAsync(x => x.Id == id);
return user?.Username ?? throw new UserNotFoundException(id);
}
Sync
- Crack egg 1
- Fry egg 1
- Prepare plate 1
- Serve egg 1
- Crack egg 2
- Fry egg 2
- Prepare plate 2
- Serve egg 2
- Crack egg 3
- Fry egg 3
- Prepare plate 3
- Serve egg 3
VS Async VS
- Crack and mix 3 eggs
-
Let fry 3 eggs together
-
While frying prepare 3 plates
- Serve 3 eggs
Parallel
-
3 cooks crack their egg
- 3 cooks fry their egg
- 3 cooks get their plate
- 3 cooks serve their egg
That's it for the boring part...
thanks you can leave now :)
If this was too much... :-)
https://michaelscodingspot.com/2019/01/17/c-deadlocks-in-depth-part-1/
https://michaelscodingspot.com/2019/01/24/c-deadlocks-in-depth-part-2/
Locking and Deadlocks explained by example with async / await
https://odetocode.com/blogs/scott/archive/2019/03/04/await-the-async-letdown.asp
thoughts by a senior C# developer and author on why working with async / await is hard and prone to errors
an excellent summary of multiple how-to and don’ts with async / await in dotnet core
https://dev.to/stuartblang/miscellaneous-c-async-tips-3o47
how to deal with lazy initialization in CTOR that has to become async (AsyncLazy example)
http://blog.i3arnon.com/2015/07/02/task-run-long-running/
TaskCreationOptions.LongRunning explained, one can read that an actual thread (not from the thread pool) is created
http://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html - SyncContext in ASP.NET Core and dangers one can face
https://msdn.microsoft.com/en-us/magazine/dn802603.aspx
a detailed overview how async applies to the ASP.NET runtime, how thread-pool allocates threads and when does context switching happen
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
the evergreen example of why not to block async calls with .Result or .Wait() in traditional environments which have a dedicated syncContext
https://msdn.microsoft.com/en-us/magazine/gg598924.aspx
why we need the synchronization context
https://markheath.net/post/async-antipatterns
a summary of all the examples and more, fresh 2019 article
https://www.youtube.com/watch?v=av5YNd4X3dY
correcting Common Mistakes When Using Async/Await in .NET - Brandon Minnick
https://www.youtube.com/watch?v=Al8LrBKpZEU
Back to Basics: Efficient Async and Await - Filip Ekberg
https://dev.to/hardkoded/a-fairy-tale-about-async-voids-events-and-error-handling-1afi
real world example of async void impact
https://www.youtube.com/watch?v=-cJjnVTJ5og
“Why your ASP.NET Core application won’t scale?”, has a lot of cool async trap examples at the end - D. Edwards and D. Fowler at NDC London 2019
https://www.youtube.com/watch?v=ghDS4_NFbcQ
“The promise of an async future awaits” - overview of async and await and it’s issues, Bill Wagner (.NET docs team) at NDC London 2019, explaining Sync, Async and Parallel with making an English breakfast example.
https://www.youtube.com/watch?v=XpgN7y_EXps
“Async & Await (You’re doing it wrong)”, good examples of bad usage: async void, async void lambda, async constructor and property, running async in sync (blocking examples)
https://chrisstclair.co.uk/demystifying-async-await
a quick and simple example of how bad application blocks the UI and how to quickly fix it.
https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d
when and how can a deadlock occur with async / await, a table view breakdown depending in which execution context the task is run, excellent overview
https://olitee.com/2015/01/c-async-await-common-deadlock-scenario/
a deadlock scenario explained
https://devblogs.microsoft.com/pfxteam/asyncawait-faq/
probably the best and concrete FAQ on async / await there is on the internet
https://devblogs.microsoft.com/pfxteam/await-and-ui-and-deadlocks-oh-my/
the most simple and right explanation of deadlock common scenario when the UI thread is blocked due to .Result or Wait() and can not restore new state from the updated SynchronizationContext (i.e. windows message loop when working with Windows Forms and WPF).
Takeaways
- do not overengingeer with Tasks, code review with care
- when working with TPL use async / await
- go async all the way from the start to the end
- beware of SynchronizationContexts and deadlocks
- Tweak the ThreadPool if needed
- Measure all cases, memory and time
- Do not spawn Task.Run() if not utterly needed
- try-catch in async void, legitimate in event handlers
- use async APIs: Task.Delay(), DbSet<T>.FirstAsync()...
- async saves memory if not abused, else the opposite
- https://github.com/davidfowl/AspNetCoreDiagnosticScenarios
The end! 📸 Questions?
VEDRAN MANDIĆ
mandic.vedran@gmail.com
@vekzdran @vmandic
bit.ly/syskit-async-await
How to (not) use async await (10+ examples)
By Vedran Mandić
How to (not) use async await (10+ examples)
Still feeling unsure when you write an async method in C# with .NET? Well, I got you covered, example by example where you might've failed. This presentation will cover basic terminology and the common async / await pitfalls which might cost you your job.
- 1,260