Kotlin分页API结果封装为Sequence的实现与疑问
协程分页API处理的疑问解答
1. runBlocking内的API请求是否仍在IO调度器执行?
答案是否定的,这段代码里runBlocking内的API请求并不会稳定在Dispatchers.IO上执行,核心原因是Sequence是惰性执行的:
你外层的withContext(Dispatchers.IO)只是创建了Sequence对象,但Sequence内部的循环逻辑只会在你遍历这个Sequence的时候才会运行。而runBlocking默认不会切换线程,它会阻塞当前执行遍历操作的线程来等待API请求完成——举个例子,如果调用getAllRowsFromAPI后在主线程遍历Sequence,那API请求就会在主线程上执行,这显然会阻塞UI,完全违背了你用Dispatchers.IO的初衷。
另外,在suspend函数里使用runBlocking本身就不太合适,它会破坏协程的非阻塞特性,建议尽量避免这种写法。
2. 是否可以重构代码,在返回当前结果前发起下一次请求?
当然可以!我们可以利用协程的async来实现预取下一页,让当前页的结果返回时,下一页的请求已经在后台执行了,能有效减少整体的等待时间。这里是重构后的代码:
suspend fun getAllRowsFromAPI(client: Client): Sequence<Row> { return sequence { var nextRequest = client.requestForNextPage() // 提前发起第一页的异步请求 var deferredNextPage: Deferred<List<Row>>? = nextRequest?.let { coroutineScope { async(Dispatchers.IO) { client.makeRequest(it) } } } while (deferredNextPage != null) { // 等待当前页请求完成 val currentPage = deferredNextPage.await() // 返回当前页的所有行 yieldAll(currentPage) // 获取下一页的请求参数,并立即发起异步请求 nextRequest = client.requestForNextPage() deferredNextPage = nextRequest?.let { coroutineScope { async(Dispatchers.IO) { client.makeRequest(it) } } } } } }
重构后的核心要点:
- 用
async(Dispatchers.IO)明确指定API请求在IO调度器执行,彻底避免阻塞主线程 - 在处理当前页结果之前,就异步发起下一页的请求,实现预取优化
- 用
coroutineScope管理异步任务,确保如果Sequence的遍历被取消(比如用户停止加载),对应的异步请求也会被取消,避免资源泄漏 - 去掉了不必要的
runBlocking,完全用协程的异步特性来处理非阻塞请求
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

