How do Kotlin Coroutines handle asynchronous programming, and how do they differ from traditional thread-based concurrency?