Kotlin withTimeout协程取消
Kotlin withTimeout coroutine cancellation
我第一次尝试协程函数withTimeout
。我正在尝试从 Android 中的设备 GPS 获取当前位置,并在没有可用位置的情况下添加超时。我无法控制获取位置的过程,因此无法轻松取消它。
更新:我最终得到了自定义超时逻辑,因为 android 本机 api 不可取消:
suspend fun LocationManager.listenLocationUpdate(): Location? =
withTimeoutOrNull(TIMEOUT) {
locationManager.listenLocationUpdate("gps")
}
private suspend fun LocationManager.listenLocationUpdate(provider: String) =
suspendCoroutine<Location?> { continuation ->
requestLocationUpdates(provider, 1000, 0f, object: TimeoutLocationListener{
override fun onLocationChanged(location: Location?) {
continuation.resume(location)
this@listenLocationUpdate.removeUpdates(this)
}
})
}
所以请求位置的过程属于sdk,我不能轻易取消它。有什么建议吗?
要使 withTimeout[OrNull]
正常工作,您需要一个协作式可取消协程。如果您调用的函数是阻塞的,它将不会按预期工作。调用协程根本不会恢复,更不用说停止阻塞方法的处理了。您可以检查 this playground code 来确认这一点。
如果要构建可取消的基于协程的 API,首先必须有一个可取消的 API。但是,如果不知道您正在调用的确切函数,就很难回答您的问题。
使用Android的LocationManager
,你可以将getCurrentLocation包装成一个可取消的挂起函数(此函数仅在API 30+级可用) :
@RequiresApi(Build.VERSION_CODES.R)
@RequiresPermission(anyOf = [permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION])
suspend fun LocationManager.getCurrentLocation(provider: String, executor: Executor): Location? = suspendCancellableCoroutine { cont ->
val signal = CancellationSignal()
getCurrentLocation(provider, signal, executor) { location: Location? ->
cont.resume(location)
}
cont.invokeOnCancellation {
signal.cancel()
}
}
否则,您也可以使用 callbackFlow
将基于侦听器的 API 转换为可取消的基于流的 API,取消订阅(通过删除侦听器):
@OptIn(ExperimentalCoroutinesApi::class)
@RequiresPermission(anyOf = [permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION])
fun LocationManager.locationUpdates(provider: String, minTimeMs: Long, minDistance: Float = 0f): Flow<Location> =
callbackFlow {
val listener = LocationListener { location -> sendBlocking(location) }
requestLocationUpdates(provider, minTimeMs, minDistance, listener)
awaitClose {
removeUpdates(listener)
}
}
如果您只想更新一次,可以在返回的流程上使用 first()
,这将自动支持取消:
suspend fun LocationManager.listenLocationUpdate(): Location? =
withTimeoutOrNull(TIMEOUT) {
locationManager.locationUpdates("gps", 1000).first()
}
如果您在位置请求中使用 numUpdates = 1
,您也应该能够将基于侦听器的 API 包装到单次暂停功能中。此处的取消可以通过删除侦听器来完成。
我第一次尝试协程函数withTimeout
。我正在尝试从 Android 中的设备 GPS 获取当前位置,并在没有可用位置的情况下添加超时。我无法控制获取位置的过程,因此无法轻松取消它。
更新:我最终得到了自定义超时逻辑,因为 android 本机 api 不可取消:
suspend fun LocationManager.listenLocationUpdate(): Location? =
withTimeoutOrNull(TIMEOUT) {
locationManager.listenLocationUpdate("gps")
}
private suspend fun LocationManager.listenLocationUpdate(provider: String) =
suspendCoroutine<Location?> { continuation ->
requestLocationUpdates(provider, 1000, 0f, object: TimeoutLocationListener{
override fun onLocationChanged(location: Location?) {
continuation.resume(location)
this@listenLocationUpdate.removeUpdates(this)
}
})
}
所以请求位置的过程属于sdk,我不能轻易取消它。有什么建议吗?
要使 withTimeout[OrNull]
正常工作,您需要一个协作式可取消协程。如果您调用的函数是阻塞的,它将不会按预期工作。调用协程根本不会恢复,更不用说停止阻塞方法的处理了。您可以检查 this playground code 来确认这一点。
如果要构建可取消的基于协程的 API,首先必须有一个可取消的 API。但是,如果不知道您正在调用的确切函数,就很难回答您的问题。
使用Android的LocationManager
,你可以将getCurrentLocation包装成一个可取消的挂起函数(此函数仅在API 30+级可用) :
@RequiresApi(Build.VERSION_CODES.R)
@RequiresPermission(anyOf = [permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION])
suspend fun LocationManager.getCurrentLocation(provider: String, executor: Executor): Location? = suspendCancellableCoroutine { cont ->
val signal = CancellationSignal()
getCurrentLocation(provider, signal, executor) { location: Location? ->
cont.resume(location)
}
cont.invokeOnCancellation {
signal.cancel()
}
}
否则,您也可以使用 callbackFlow
将基于侦听器的 API 转换为可取消的基于流的 API,取消订阅(通过删除侦听器):
@OptIn(ExperimentalCoroutinesApi::class)
@RequiresPermission(anyOf = [permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION])
fun LocationManager.locationUpdates(provider: String, minTimeMs: Long, minDistance: Float = 0f): Flow<Location> =
callbackFlow {
val listener = LocationListener { location -> sendBlocking(location) }
requestLocationUpdates(provider, minTimeMs, minDistance, listener)
awaitClose {
removeUpdates(listener)
}
}
如果您只想更新一次,可以在返回的流程上使用 first()
,这将自动支持取消:
suspend fun LocationManager.listenLocationUpdate(): Location? =
withTimeoutOrNull(TIMEOUT) {
locationManager.locationUpdates("gps", 1000).first()
}
如果您在位置请求中使用 numUpdates = 1
,您也应该能够将基于侦听器的 API 包装到单次暂停功能中。此处的取消可以通过删除侦听器来完成。