首页 > 解决方案 > suspendCancellableCoroutine 返回 CompletedWithCancellation 而不是实际类型

问题描述

kotlinx-coroutines-core当我将依赖项从 1.3.2 更新到 1.3.3时,我遇到了一个奇怪的问题。但是,下面的独立示例也重现了 1.3.2 的问题。

我有一个基于回调的操作队列的扩展方法。此扩展方法用于suspendCancellableCoroutine包装回调用法并将其转换为挂起函数。现在,一切都可以正常工作,但是从挂起函数返回的结果对象不是类型T,而是CompletedWithCancellation<T>,它是协程库的私有类。

奇怪的是,如果我c.resume("Foobar" as T, {})在内部调用suspendCancellableCoroutine,它工作得很好。使用回调例程时,值是 aString在传递到 to 之前c.resume(),但它被包装在一个CompletedWithCancellation对象中。

这是重现该问题的代码:

@ExperimentalCoroutinesApi
class MainActivity : AppCompatActivity() {

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Timber.plant(Timber.DebugTree())
        setContentView(R.layout.activity_main)

        val vm = ViewModelProviders.of(this)
                .get(MainViewModel::class.java)

        vm.liveData.observe(this, Observer {
            findViewById<TextView>(R.id.mainText).text = "Got result: $it"
        })

        vm.getFoo()
    }
}

@ExperimentalCoroutinesApi
class MainViewModel : ViewModel() {

    private val manager = OperationManager()
    val liveData = MutableLiveData<String>()

    fun getFoo() {
        viewModelScope.launch {
            val op = Operation(manager, "Foobar")
            val rawResult = op.get<Any>()
            Timber.d("Raw result: $rawResult")

            val op2 = Operation(manager, "Foobar")
            val result = op2.get<String>()
            Timber.d("Casted result: $result")
            liveData.postValue(result)
        }
    }
}

class OperationManager {
    private val operationQueue = ConcurrentLinkedQueue<Operation>()
    private val handler = Handler(Looper.getMainLooper())
    private val operationRunnable = Runnable { startOperations() }

    private fun startOperations() {
        val iter = operationQueue.iterator()
        while (iter.hasNext()) {
            val operation = iter.next()
            operationQueue.remove(operation)
            Timber.d("Executing operation $operation")
            operation.onSuccess(operation.response)
        }
    }

    fun run(operation: Operation) {
        addToQueue(operation)
        startDelayed()
    }

    private fun addToQueue(operation: Operation) {
        operationQueue.add(operation)
    }

    private fun startDelayed() {
        handler.removeCallbacks(operationRunnable)
        handler.post(operationRunnable)
    }
}

open class Operation(private val manager: OperationManager, val response: Any) {

    private val listeners = mutableListOf<OperationListener>()

    fun addListener(listener: OperationListener) {
        listeners.add(listener)
    }

    fun execute() = manager.run(this)
    fun onSuccess(data: Any) = listeners.forEach { it.onResult(data) }
}

@ExperimentalCoroutinesApi
suspend fun <T> Operation.get(): T = suspendCancellableCoroutine { c ->

    @Suppress("UNCHECKED_CAST")
    val callback = object : OperationListener {
        override fun onResult(result: Any) {
            Timber.d("get().onResult() -> $result")
            c.resume(result as T, {})
        }
    }

    addListener(callback)
    execute()
}

interface OperationListener {
    fun onResult(result: Any)
}

请注意,在调用之前c.resume(),它的类型应该是resultString但是,一旦挂起功能完成,它就不会String出现。getFoo()这是什么原因造成的?

标签: androidkotlinkotlin-coroutines

解决方案


解决方案是:

c.resume(result as T)

代替:

c.resume(result as T, {})

似乎前者处理了resume()正确getResult()调用 after 的执行,而后者仅在resume()调用 before时才有效getResult()


推荐阅读