In an async context, each
await point introduces a suspend point of the async routine, at which point the routine can be a) resumed (happy path) or b) cancelled/dropped (“surprising” path).
Consideration of not holding locks (and similar) is easily explained as that
async is long-running. As well as any other long-running sync call that doesn’t depend on the lock, you shouldn’t be holding the lock.
The cancellation problem is harder, though. The closest sync equivalent is probably panic-correctness (which is just as important to consider! but often forgotten as the “crash path”).
Having to worry about it is even more common as well, as async code implies synchronization of progress of some amount, so manipulation of synchronization primitives is more likely in async contexts.
First: can this create issues with purely safe code (while also being useful; of course you could just spinloop a CAS anyway)? I think anything manipulating primitives in a useful manner will require being within an unsafe barrier, which should suggest the need for greater scrutiny to begin with.
Second: is there much we can do beyond making it clear in intermediate level teaching that
await introduces a cancellation edge? It seems similar to
? in this fashion, but in the case of
? you’re getting the exit edge by request, by
await you’re getting it as an extra tag along from the primary effect.