如何在超时后取消协程作业



具有挂起函数fetchData(). 它所做的是在withContext中启动一些作业,这样它只会在作业完成后返回(即:suspend fun getData(): Boolean(。

并且还希望如果它超时,则从函数返回 false。

问题是当它超时时,withTimeoutOrNull(500) { jobs.joinAll() },它卡在函数中不退出。

日志显示它超时,还清楚地指向退出函数之前代码的最后一行:

E/+++: +++ in fetchData() after null = withTimeoutOrNull(500), jobs.sizs: 3
E/+++: +++ --- exit fetchData(), allFresh: false

但是fetchData()的呼叫者卡住了,没有从fetchData()回来。

这是调用方:

suspend fun caller() {
var allGood = fetchData()
// never return to here
Log.e("+++", "+++ caller(), after allGood: $allGood = fetchData()")
...
}

下面是代码,超时如何取消作业?

suspend fun fetchData(): Boolean = withContext(Dispatchers.IO) {
var allFresh = requestHandlertMap.size > 0
if (!allFresh) {
allFresh
} else {
val handlers = requestHandlertMap.values.toList()
val jobs: List<Deferred<Boolean>> = handlers.map {handler->
async(start = CoroutineStart.LAZY) {
if (isActive) handler.getData() else true
.also {
Log.e("+++", "+++ in fetchData():async{} after handler.getData()")
}
}
}
val result = withTimeoutOrNull(500) { jobs.joinAll() }
Log.e("+++", "+++ in fetchData() after $result = withTimeoutOrNull(500), jobs.size: ${jobs.size} ")
if (result != null) {
allFresh = jobs.all { deferred ->
deferred.await()
}
Log.e("+++", "+++ +++ +++ in fetchData() call  onDataReady(), allFresh: $allFresh = deferred.await() ")
onDataReady()
} else {
// how to cancel the jobs ???
//jobs.all { deferred ->
//deferred.cancelChildren()
//}
allFresh = false
}
allFresh
.also {
Log.e("+++", "+++ --- exit fetchData(), allFresh: $allFresh  ")
}
}
}

经过一些阅读/尝试,似乎在实现方面存在一些问题。

  1. 不知何故,CoroutineStart.LAZY导致了一种奇怪的行为,即async(start = CoroutineStart.LAZY)按顺序启动(期望它们应该开始并发(,以便在超时时它卡在函数中(猜测,因为它被包装在withContext(Dispatchers.IO)中并且并非所有子协程都已完成 - 如果有人还没有启动(。

移除start = CoroutineStart.LAZY使其从fun fetchData()返回

val jobs: List<Deferred<Boolean>> = handlers.map {handler->
async(start = CoroutineStart.LAZY) {
if (isActive) handler.getData() else true
.also {
Log.e("+++", "+++ in fetchData():async{} after handler.getData()")
}
}
}
  1. suspend fun getData(): Boolean没有cooperate to be cancellable实现,这可能会导致它仍然留在函数中,直到所有子级都已完成,尽管超时已经发生。

  2. 似乎还需要调用deferred.cancelChildren(),否则它们不会被withTimeoutNotNull()取消,不知道为什么,它不应该自动取消作业吗?

所做的更改

private suspend fun fetchData(): Boolean {
var allFresh: Boolean? = requestHandlertMap.size > 0
if (allFresh == true) {
val handlers = requestHandlertMap.values.toList()
val jobs: List<Deferred<Boolean>> = handlers.map {
serviceScope.async(start = CoroutineStart.DEFAULT) { handler -> if (isActive) handler.getData() else false }
}
allFresh = withTimeoutOrNull(3000) {
try {
jobs.awaitAll().all { it }
} catch (ex: Throwable) {
false
}
}
if (allFresh != null) {
onDataReady()
} else {
jobs.map { deferred -> deferred.cancelChildren() }
}
}
return allFresh == true // allFresh = {null, true, false}
}

参考: 这里和这里

最新更新