Switching async tasks should have a smaller overhead than switching threads. A context switch involves giving control to the OS and then the OS giving it back at some point. Both involve lots of cache trashing, as you said, and both switches involve bookkeeping that the OS has to do. This probably also involves instruction-cache evictions.
Switching async tasks involves loading the new task from memory onto the stack. That's it. The program can immediately start doing useful work again.
So, the short answer is that switching threads involves:
- more cache evictions
- bigger, slower cache evictions
- OS-level bookkeeping for threads
Also creating threads involves setting up a new, expandable stack in the process's memory space. Creating a new task involves allocating a fixed amount of memory on the heap once.