suspendCancellableCoroutine возвращает CompletedWithCancellation вместо фактического типа - PullRequest
0 голосов
/ 28 апреля 2020

Я столкнулся со странной проблемой, которая проявилась, когда я обновил зависимость kotlinx-coroutines-core с 1.3.2 до 1.3.3. Тем не менее, автономный пример ниже воспроизводит проблему с 1.3.2.

У меня есть метод расширения для очереди операций на основе обратного вызова. Этот метод расширения использует suspendCancellableCoroutine, чтобы обернуть использование обратного вызова и преобразовать его в функцию приостановки. Теперь все работает иначе, но результирующий объект, который возвращается из функции приостановки, имеет тип T, а не CompletedWithCancellation<T>, который является частным классом библиотеки сопрограмм.

Странная вещь есть, если я позвоню c.resume("Foobar" as T, {}) внутри suspendCancellableCoroutine, это работает просто отлично. При использовании подпрограммы обратного вызова значение передается в c.resume(), равное String, но оно оборачивается в 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() тип result равен String, как и должно быть. Тем не менее, это не String в getFoo() после завершения функции приостановки. Что вызывает это?

1 Ответ

0 голосов
/ 28 апреля 2020

Решение было в следующем:

c.resume(result as T)

Вместо:

c.resume(result as T, {})

Кажется, что первый обрабатывает выполнение resume() правильно после вызова getResult(), тогда как последний работает, только если resume() вызывается раньше getResult().

...