Async Tip #2: It's crucial to distinguish CPU-bound work (should be done on threadpool) from IO-bound work (which needn't).
I remember reading an old Android dev blog post. It said: "1. A good practice in creating responsive applications is to make sure your main UI thread does the minimum amount of work. 2. Any potentially long task that may hang your application should be handled in a different thread. 3. Typical examples of such tasks are network operations, which involve unpredictable delays."
There's a serious flaw in this logic... When your program is just sitting there twiddling its thumbs, waiting for a network packet to come back, it's not doing any work, and so doesn't need to go on a background thread. You should await it instead!
Await opens up a whole new simpler world of programming. You can mostly get by without any background threads at all. That means all of your code can run on the UI thread, which makes it much easier to do databinding, to update the UI, and so on.
Await also improves responsiveness of servers. That's because the threadpool takes some time to ramp up. If you use await, then you can ramp up instantly.
So when do you need to use the threadpool, and how? Answer: only when you've got some CPU-bound work, like a compute-bound iteration over a large dataset; and do it using Parallel.ForEach or Task.Run.