C# async ⚡️ await
is not your playground

 #DevUgZg, Zagreb 2019-11-27
http://bit.ly/DevUgZg-async-await

Vedran Mandić, MCSD, MCSA, MCT

Hi, I am Vedran. 👋

  • I freelance, talk and lecture C# / .NET and JavaScript
  • currently in insurance 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 🙈

Plan?

  1. What is so hard with (C#) async code?
  2. Must-know 🌟 terminology, (PRO)TIPS!
  3. async / await FAQ 10+ examples
    • demo code 👩‍💻
  4. Q&A (please after 3) ⏰⏰⏰
  5. Study, share and apply, please...

30min + 30min (very fast tempo)

Why is async...

...code hard to grasp?

System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: Operation is not valid due to the current state of the object.
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 29
   --- End of inner exception stack trace ---
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 39
   --- End of inner exception stack trace ---
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 51
---> (Inner Exception #0) System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: Operation is not valid due to the current state of the object.
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 29
   --- End of inner exception stack trace ---
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 39
---> (Inner Exception #0) System.InvalidOperationException: Operation is not valid due to the current state of the object.
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 29<---

---> (Inner Exception #1) System.FormatException: One of the identified items was in an invalid format.
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 35<---
<---

---> (Inner Exception #1) System.NotImplementedException: The method or operation is not implemented.
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 47<---
System.AggregateException: One or more errors occurred. ---> System.NotImplementedException: The method or operation is not implemented.
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 47
   --- End of inner exception stack trace ---
---> (Inner Exception #0) System.NotImplementedException: The method or operation is not implemented.
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 47<---

---> (Inner Exception #1) System.InvalidOperationException: Operation is not valid due to the current state of the object.
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 29<---

---> (Inner Exception #2) System.FormatException: One of the identified items was in an invalid format.
   at ConsoleApplication6.Program.Main(String[] args) in C:\Projects\ConsoleApplication6\ConsoleApplication6\Program.cs:line 35<---
  • understanding parallel vs async
  • APM, EAP, BackgroundWorker, Parallel, TPL or async / await !?
  • "types" and "roles" of threads ?
  • how / why to create a new Thread() ?
  • how / why to run a new Task() ? Should I use .ContinueWith() ?
  • what is a SynchronizationContext ?
  • what is ThreadPool and TaskScheduler, how do they execute ?
  • what is Thread synchronization or joining ?
  • what are locks, semaphors and monitors ?
  • why does my Windows app crash from another thread ?
  • difference or link between Thread and Task ?
  • when to use async / await syntax, why avoid async void ?
  • why are Task exceptions swollen, why do I get Deadlocks ?
  • when to use Task or ValueTask, what is IAsyncEnumerable !?
  • what is .ConfigureAwait(false) and WHY should(n't) I use it ?
  • what happend in .NET Core with SynchronizationContext !?!?
  • what is "sync over async" and why you should not do it ?
  • SHOULD I USE ASYNC ALAWAYS ?
  • IS IT FASTER OR BETTER !?

📸

This is why
C# async code is hard...

Some...
(raged but on point) community feedback on C# async design

async void: I wish we could have not allowed that...


- Mads Torgersen [MSFT], Oct 24 2019

(very boring) Important

Terminology Recap

You can take a sip of 🍺 now...

System.Threading.Thread

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

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

thread 1

thread 42

thread 3

thread 114

thread 3

thread 1

System.Threading.Tasks.Task

  • from .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

System.Threading.SynchronizationContext

System.Threading.SynchronizationContext 1/2

  • represents a "location" where || how a Thread is run
  • 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!

System.Threading.SynchronizationContext 2/2

  • a null SynchronizationContext of a Thread is a new SynchronizationContext(), unrestricted, with access to the ThreadPool
     
  • WinForms, WPF, ASP.NET Framework have synchronizing SynchronizationContexts:
    • you can execute only one task after each other per active thread...
       
  • BEWARE: Upgrading from full .NET ASP.NET framework to ASP.NET Core may cause implicit parallelism and race condition issues


TIP: Why ASP.NET Core has no SyncContext, S. Cleary
 

TaskScheduler

of System.Threading.Tasks

TaskScheduler

System.Threading.Tasks

  • handles 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
  • inherit your own, e.g. limit number of Threads in app

How is a task scheduled?

Deadlock

Deadlock

  • one or more threads block / wait / hang on a line of code indefinitely, waiting for another thread to finish,
    the app 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
 Task.Run(() => lblInfo.Text = "Boom!");

(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

some e-z win tips...

PROTIP 1:


use async / await (all the way) if unsure or must have

no need for overengineering with Parallel or Threads

public async Task<string> GetUniqueUsernameAsync(int id) 
{
    var user = await db.Users.SingleOrDefaultAsync(x => x.Id == id);
    return user?.Username ?? throw new UserNotFoundException(id);
}

PROTIP 2:


please meassure perf!

async / await is not always good!

but... e.g. do apply it on a highly requested web endpoint

public string GetUniqueUsername(int id) 
{
    var user = db.Users.SingleOrDefault(x => x.Id == id);
    return user?.Username ?? throw new UserNotFoundException(id);
}

PROTIP 3:


avoid if possible dangerous "sync over async" blocking

good luck catching that exception or awaitng the method

public Task<string> GetUniqueUsername(int id) 
{
    // DON'T DO THIS, PLEASE... JUST DON'T
    var user = db.Users.SingleOrDefaultAsync(x => x.Id == id).Result;
    
    return Task.FromResult(user?.Username 
    	?? throw new UserNotFoundException(id));
}

PROTIP 4:


if you must do "sync over async"...

...then at least you'll get a proper exception

public string GetUniqueUsername(int id) 
{
    var user = db.Users.SingleOrDefaultAsync(x => x.Id == id)
    	.GetAwaiter()
    	.GetResult();
    return user?.Username ?? throw new UserNotFoundException(id);
}

Sync

VS     Async   VS

Parallel

Sync

  1. Crack egg 1
  2. Fry egg 1
  3. Prepare plate 1
  4. Serve egg 1
     
  5. Crack egg 2
  6. Fry egg 2
  7. Prepare plate 2
  8. Serve egg 2
     
  9. Crack egg 3
  10. Fry egg 3
  11. Prepare plate 3
  12. Serve egg 3

VS     Async   VS

  1. Crack and mix 3 eggs
     
  2. Let fry 3 eggs together
     
  3. While frying prepare 3 plates
     
  4. Serve 3 eggs

Parallel

  1. 3 cooks crack their egg
     
  2. 3 cooks fry their egg
     
  3. 3 cooks get their plate
     
  4. 3 cooks serve their egg

That's it for the boooooring part... 🔨

thanks you can leave now for pizza :)

If this was too little... :-)

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

 

https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AspNetCoreGuidance.md#aspnet-core-guidance

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
  • measure perf, don't always assume asnyc / await!
  • go async all the way from the start to the end
  • beware of SynchronizationContext and deadlocks
  • Tweak the ThreadPool if (really...) 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

http://bit.ly/DevUgZg-async-await