* 协程到底是什么?
* 为什么需要 Kotlin Coroutines 提供的解决方案?
* 关于如何在 Android 中实现 Kotlin 协程的分步指南。
* Kotlin 协程中的作用域是什么?
* Kotlin 协程中的异常处理。
* [通过示例学习 Kotlin Coroutines for Android 的项目](https://github.com/MindorksOpenSource/Kotlin-Coroutines-Android-Examples)。
协程,一个非常高效和完整的框架,以更高效和更简单的方式管理并发
什么是协程?
在这里,Co表示合作,Routines表示功能。
这意味着当函数相互协作时,我们称之为协程。
让我们通过一个例子来理解这一点。为了便于理解,我以不同的方式编写了以下代码。假设我们有两个函数functionA和functionB。
函数A如下:
fun functionA(case: Int) {
when (case) {
1 -> {
taskA1()
functionB(1)
}
2 -> {
taskA2()
functionB(2)
}
3 -> {
taskA3()
functionB(3)
}
4 -> {
taskA4()
functionB(4)
}
}
}
功能B如下:
fun functionB(case: Int) {
when (case) {
1 -> {
taskB1()
functionA(2)
}
2 -> {
taskB2()
functionA(3)
}
3 -> {
taskB3()
functionA(4)
}
4 -> {
taskB4()
}
}
}
然后,我们可以如下调用函数A:
functionA(1)
在这里,functionA将执行taskA1并将控制权交给functionB以执行taskB1。
然后,函数B将执行任务B1并将控制权交还给函数A以执行任务A2,依此类推。
重要的是functionA和functionB相互合作。
使用 Kotlin Coroutines 可以非常轻松地完成上述协作,而无需使用我在上面的示例中为了便于理解而使用的when或switch case 。
现在,我们已经了解了函数之间的协作是什么。由于功能的协作性,存在无限的可能性。
一些可能性如下:
它可以执行几行函数A,然后执行几行函数B,然后再执行几行函数A,依此类推。当线程闲置不做任何事情时,这将很有帮助,在这种情况下,它可以执行几行另一个函数。这样,它可以充分利用线程。最终,合作有助于多任务处理。
它将能够以同步方式编写异步代码。我们将在本文后面讨论这个问题。
总体而言,协程使多任务处理变得非常容易。
所以,我们可以说协程和线程都是多任务的。但不同的是,线程由操作系统管理,协程由用户管理,因为它可以利用协作执行几行功能。
它是一个在实际线程上编写的优化框架,利用函数的协作特性使其轻巧而强大。所以,我们可以说协程是轻量级线程。轻量级线程意味着它不会映射到本机线程,因此它不需要处理器上的上下文切换,因此它们更快。
Kotlin 实现了无堆栈协程——这意味着协程没有自己的堆栈,因此它们不会映射到本机线程上
Kotlin 实现了无堆栈协程——这意味着协程没有自己的堆栈,因此它们不会映射到本机线程上
##为什么需要 Kotlin 协程?
让我们以非常标准的 Android 应用程序用例为例,如下所示:
从服务器获取用户。
在 UI 中显示用户
fun fetchUser(): User {
// make network call
// return user
}
fun showUser(user: User) {
// show user
}
fun fetchAndShowUser() {
val user = fetchUser()
showUser(user)
}
当我们调用fetchAndShowUser函数时,它会抛出NetworkOnMainThreadException,因为主线程上不允许网络调用。
有很多方法可以解决这个问题。其中一些如下:
1.使用回调:在这里,我们在后台线程中运行 fetchUser,并通过回调传递结果。
fun fetchAndShowUser() {
fetchUser { user ->
showUser(user)
}
}
2.使用 RxJava:反应式世界方法。这样我们就可以摆脱嵌套回调。
fetchUser()
.subscribeOn(Schedulers.io())
.observerOn(AndroidSchedulers.mainThread())
.subscribe { user ->
showUser(user)
}
3.使用协程:是的,协程。
suspend fun fetchAndShowUser() {
val user = fetchUser() // fetch on IO thread
showUser(user) // back on UI thread
}
在这里,上面的代码看起来是同步的,但实际上是异步的。我们将看看这怎么可能。
Android 中 Kotlin 协程的实现
在 Android 项目中添加 Kotlin Coroutines 依赖项,如下所示:
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
}
现在,我们的函数fetchUser将如下所示:
suspend fun fetchUser(): User {
return GlobalScope.async(Dispatchers.IO) {
// make network call
// return user
}.await()
}
逐步学习suspend、GlobalScope、async、await和Dispatchers.IO。
fetchAndShowUser 如下所示:
suspend fun fetchAndShowUser() {
val user = fetchUser() // fetch on IO thread
showUser(user) // back on UI thread
}
1.调度程序:调度程序帮助协程决定必须在哪个线程上完成工作。主要有三种类型的 Dispatcher,分别是IO、Default 和 Main。IO dispatcher 用来做网络和磁盘相关的工作。默认用于执行 CPU 密集型工作。Main 是 Android 的 UI 线程。为了使用这些,我们需要将工作包装在async函数下。异步函数如下所示。
suspend fun async() //
挂起:挂起函数是一个可以启动、暂停和恢复的函数。
挂起函数只允许从协程或另一个挂起函数中调用。您可以看到包含关键字suspend的async函数。所以,为了使用它,我们需要让我们的函数也暂停。
因此,fetchAndShowUser只能从另一个挂起函数或协程调用。我们不能让Activity的 onCreate 函数挂起,所以我们需要从协程中调用它,如下所示:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch(Dispatchers.Main) {
fetchAndShowUser()
}
}
这实际上是
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch(Dispatchers.Main) {
val user = fetchUser() // fetch on IO thread
showUser(user) // back on UI thread
}
}
howUser 将在 UI 线程上运行,因为我们使用了Dispatchers.Main来启动它。
Kotlin 中有两个函数可以启动协程,如下所示:
launch{}
async{}
Kotlin 协程中的启动与异步
不同之处在于launch{}不返回任何内容
async{}返回 的实例Deferred<T>,该实例具有await()返回协程结果的函数,就像我们在 Java 中使用 future.get() 来获取结果一样。
换一种说法:
launch:
async: 执行任务并返回结果
我们举个例子来学习launch和async。
我们有一个 fetchUserAndSaveInDatabase 函数,如下所示:
suspend fun fetchUserAndSaveInDatabase() {
// fetch user from network
// save user in database
// and do not return anything
}
现在,我们可以使用如下启动:
GlobalScope.launch(Dispatchers.Main) {
fetchUserAndSaveInDatabase() // do on IO thread
}
由于fetchUserAndSaveInDatabase不返回任何内容,我们可以使用启动来完成该任务,然后在主线程上执行一些操作。
但是当我们需要返回结果时,我们需要使用async。
我们有两个返回 User 的函数,如下所示:
suspend fun fetchFirstUser(): User {
// make network call
// return user
}
suspend fun fetchSecondUser(): User {
// make network call
// return user
}
现在,我们可以像下面这样使用异步:
GlobalScope.launch(Dispatchers.Main) {
val userOne = async(Dispatchers.IO) { fetchFirstUser() }
val userTwo = async(Dispatchers.IO) { fetchSecondUser() }
showUsers(userOne.await(), userTwo.await()) // back on UI thread
}
有一种叫做withContext的东西。
suspend fun fetchUser(): User {
return GlobalScope.async(Dispatchers.IO) {
// make network call
// return user
}.await()
}
with
Context 只不过是另一种编写异步的方式,我们不必编写await()。
suspend fun fetchUser(): User {
return withContext(Dispatchers.IO) {
// make network call
// return user
}
}
withC
ontext但是关于和,我们还应该知道更多的事情await。
现在,让withContext我们在我们的async示例中并行使用fetchFirstUserand 。fetchSecondUser
GlobalScope.launch(Dispatchers.Main) {
val userOne = withContext(Dispatchers.IO) { fetchFirstUser() }
val userTwo = withContext(Dispatchers.IO) { fetchSecondUser() }
showUsers(userOne, userTwo) // back on UI thread
}
当我们使用withContext时,它将串联而不是并行运行。这是一个主要区别。
规则:
withContext当您不需要并行执行时使用。
async仅在需要并行执行时使用。
两者withContext和async都可用于获得launch.
用于withContext返回单个任务的结果。
用于async并行运行的多个任务的结果
Kotlin 协程中的作用域
假设我们的活动是范围,一旦活动被销毁,后台任务应该被取消。
在活动中,我们需要实现 CoroutineScope
lass MainActivity :
AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
private lateinit var job: Job
}
在 onCreate 和 onDestroy 函数中。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job() // create the Job
}
override fun onDestroy() {
job.cancel() // cancel the Job
super.onDestroy()
}
现在,只需使用如下启动:
launch {
val userOne = async(Dispatchers.IO) { fetchFirstUser() }
val userTwo = async(Dispatchers.IO) { fetchSecondUser() }
showUsers(userOne.await(), userTwo.await())
}
一旦活动被销毁,如果它正在运行,任务将被取消,因为我们已经定义了范围。
当我们需要全局范围,即我们的应用范围,而不是活动范围时,我们可以使用 GlobalScope,如下所示:
GlobalScope.launch(Dispatchers.Main) {
val userOne = async(Dispatchers.IO) { fetchFirstUser() }
val userTwo = async(Dispatchers.IO) { fetchSecondUser() }
}
在这里,即使活动被破坏, fetchUser 函数将继续运行,因为我们使用了GlobalScope。
如果其中一个网络调用失败,它将直接进入catch区块。
但是假设,我们想为失败的网络调用返回一个空列表,并继续来自另一个网络调用的响应。我们可以将try-catch 块添加到单个网络调用中,如下所示:
launch {
try {
val users = try {
getUsers()
} catch (e: Exception) {
emptyList<User>()
}
val moreUsers = try {
getMoreUsers()
} catch (e: Exception) {
emptyList<User>()
}
} catch (exception: Exception) {
Log.d(TAG, "$exception handled !")
}
}
这样,如果出现任何错误,它将继续使用空列表。
现在,如果我们想并行进行网络调用怎么办。我们可以使用async.
launch {
try {
val usersDeferred = async { getUsers() }
val moreUsersDeferred = async { getMoreUsers() }
val users = usersDeferred.await()
val moreUsers = moreUsersDeferred.await()
} catch (exception: Exception) {
Log.d(TAG, "$exception handled !")
}
}
在这里,我们将面临一个问题,如果出现任何网络错误,应用程序就会崩溃!,它不会去catch块。
为了解决这个问题,我们将不得不使用coroutineScope下面的方法:
launch {
try {
coroutineScope {
val usersDeferred = async { getUsers() }
val moreUsersDeferred = async { getMoreUsers() }
val users = usersDeferred.await()
val moreUsers = moreUsersDeferred.await()
}
} catch (exception: Exception) {
Log.d(TAG, "$exception handled !")
}
}
现在,如果出现任何网络错误,它将进入catch块。
但是再次假设,我们想为失败的网络调用返回一个空列表,并继续来自另一个网络调用的响应。我们将不得不使用supervisorScope并将try-catch块添加到单个网络调用中,如下所示:
launch {
try {
supervisorScope {
val usersDeferred = async { getUsers() }
val moreUsersDeferred = async { getMoreUsers() }
val users = try {
usersDeferred.await()
} catch (e: Exception) {
emptyList<User>()
}
val moreUsers = try {
moreUsersDeferred.await()
} catch (e: Exception) {
emptyList<User>()
}
}
} catch (exception: Exception) {
Log.d(TAG, "$exception handled !")
}
}
同样,这样,如果出现任何错误,它将继续使用空列表。
这就是supervisorScope帮助。
结论:
虽然不使用async,但我们可以继续使用try-catchorCoroutineExceptionHandler并根据我们的用例实现任何目标。
在使用async时,除了try-catch,我们还有两个选择:coroutineScope和supervisorScope。
使用async,当您想要在其中一个或部分任务失败时继续执行其他任务时,除了顶级任务之外,还可以supervisorScope与个人一起使用。try-catchtry-catch
使用async,coroutineScope与顶级 一起使用try-catch,当您不想继续其他任务(如果其中任何一个任务失败)时。