协程和Okhttp中的取消 [英] Cancellation in Coroutines and Okhttp

查看:128
本文介绍了协程和Okhttp中的取消的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,我正在使用Coroutines和Okhttp连接网络套接字.

So I'm playing around using Coroutines and Okhttp to connect a websocket.

// initialise okhttp
fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(RetryInterceptor())
            .build()
}

// RetryInterceptor.kt
class RetryInterceptor : Interceptor {

    companion object {
        private const val RETRIES_LIMIT = 4
    }

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        var retries = 0

        var response: Response?
        response = sendRequest(chain, request)

        while (response == null && retries <= RETRIES_LIMIT) {
            retries++
            val sleepTimer = 2.toDouble().pow(retries.toDouble())
            Log.d("OkhttpClient", "Connection failed, retry in ${sleepTimer}s")
            Thread.sleep(sleepTimer.toLong() * 1000)
            response = sendRequest(chain, request)
        }

        return response ?: Response.Builder()
            .request(request)
            .code(400)
            .build()
    }

    private fun sendRequest(chain: Interceptor.Chain, request: Request): Response? {
        val response: Response
        return try {
            response = chain.proceed(request)
            if (!response.isSuccessful) null else response
        } catch (e: IOException) {
            null
        }
    }
}

// define a exception handler
val handler = CoroutineExceptionHandler { _, throwable ->
        when (throwable) {
            is CancellationException -> {
                // cancel the socket connection here
                Log.d("CancellationException", "cancelled")
            }
            else -> onRegisterError(
                throwable.localizedMessage ?: "Coroutine Error"
            )

        }
    }

// Then inside ViewModel, fire up the okhttp client
val viewModelScopeJob = viewModelScope.launch(context = handler) {

            val someOtherJob = otherContext.launch {
                // launch suspend fun connectSocket()
            }

        }
// Then call cancel inside ViewModel like this:
viewModelScopeJob.cancel()

问题

viewModelScopeJob 是父作业,当调用 cancel()时,应取消其子作业并调用 CancellationException ,但事实并非如此.

Problem

viewModelScopeJob is a parent job, when the cancel() is being called, it should cancel its child-jobs and invoke the CancellationException, however it doesn't.

因此协程作业不会因为拦截器内的 Thread.sleep()不合作而被取消.

So the coroutine job will not be cancelled because of Thread.sleep() inside interceptor is not cooperative.

我的问题是:鉴于 RetryInterceptor 位于单独的类中,我无法使用 delay()之类的方法,我该如何更改代码为了取消重试 viewModelScopeJob.cancel()时的重试?

My questions is: given RetryInterceptor is located in a separate class, I'm not be able to use methods like delay(), how should I change my code in order to cancel the retry when viewModelScopeJob.cancel() is called?

推荐答案

您需要进行两项修复.

首先,注册一个协程取消监听器,以取消OkHttp调用.您可以在中看到一个示例Retrofit的协程整合.

First, register a coroutine cancelation listener that cancels the OkHttp call. You can see an example of this in Retrofit’s coroutine integration.

continuation.invokeOnCancellation {
  cancel()
}

接下来,您需要在取消调用时中断线程睡眠.一种解决方法是使用 EventListener .覆盖取消以中断OkHttp线程.您可以使用 callStart()中的 Thread.currentThread()保存对该线程的引用.您还应该覆盖 callEnd() callFailed()以清除该保存的引用.

Next, you need to interrupt the thread sleep when the call is canceled. One way to handle this is with an EventListener. Override cancel to interrupt the OkHttp thread. You can save a reference to that thread with Thread.currentThread() in callStart(). You should also override callEnd() and callFailed() to clear that saved reference.

事件页包含有关如何注册事件侦听器工厂的更多信息,以便每个调用都有自己的 EventListener 实例.

The Events page has more information on how to register an event listener factory so that each call gets its own EventListener instance.

这篇关于协程和Okhttp中的取消的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆