Я столкнулся со странной проблемой, которая проявилась, когда я обновил зависимость 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()
после завершения функции приостановки. Что вызывает это?