I don't think there are really ways to do async programming that don't have gotchas. It's just inherently complicated.
And of course C#/.NET is a bit older and also very large, it has more surface area for weird behaviour that might not be easily fixable due to backwards compatibility.
Things like using void as a return type for an async function can be a nasty surprise if you're new, but this is not an issue at all once you know this (or if you simply used the right examples and used Task from the start). It's not a subtle gotcha.
Sync over async is much sneakier and can be very nasty. But I'm not sure you can avoid this when interacting with a language/environment that used to be mostly sync and switched to async. If everything is async you don't have this issue, so this is better for newer codebases.