In asyncio and similar systems, the choice between "is this code syncronous or asyncronous" is made at declaration time - either you define a sync function or an async one, and you can only use it in that way.
In gevent and similar systems, the same choice is made at call time - there's no distinction between sync and async functions, instead you can call syncronously with "foo()" or asyncronously with "future = gevent.spawn(foo)" (I'm taking some liberties by calling the returned greenlet object a future but it can be used as such).
I've gotten good at diagnosing these issues over many years as a heavy gevent user, and it doesn't stop me highly reccomending gevent to anyone who will listen, but it's a caveat that should be mentioned for anyone new starting out.
This is thankfully fairly rare, but it is something you need to be aware of.
Also, wouldn't asyncio probably have similar issues with native modules as well?