You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中runBlocking内执行Result异步方法时await抛出类型转换异常问题

Why runBlocking Causes Type Conversion Errors with Your Suspending Result Chain

Great question! Let's break down the root cause of this issue and walk through the fix.

What's Going Wrong?

The core problem lies in how you're creating coroutines inside suspendingFunction:

val plusOneTask = CoroutineScope(Dispatchers.Default).async { delayedPlusOne(no) }

When you use CoroutineScope(Dispatchers.Default), you're creating a completely independent coroutine scope with its own Job—this job isn't linked to the parent coroutine running inside runBlocking.

While you do call await() to wait for the async result, in the runBlocking context, this independent scope's coroutines run on Dispatchers.Default's daemon threads. In this scenario, the parent runBlocking coroutine doesn't properly wait for this unlinked child coroutine to complete, leading to await() returning a CoroutineSingletons.COROUTINE_SUSPENDED placeholder instead of the actual Int value. This placeholder gets wrapped into your Result, causing the type cast exception when you try to access it as a Number.

Interestingly, the suspend main function works because the top-level coroutine has a longer lifecycle that can wait for the independent scope's coroutines to finish.

How to Fix It

You need to tie your child coroutines to the current coroutine's scope instead of creating a new independent one. Here are two clean solutions:

Solution 1: Use coroutineScope for Child Coroutines

Replace the standalone CoroutineScope with the coroutineScope suspend function, which creates a child scope bound to the current coroutine's Job:

private suspend fun suspendingFunction(no: Int): Result<Int> {
    val plusOne = coroutineScope {
        async { delayedPlusOne(no) }.await()
    }
    return Result.success(plusOne)
}

coroutineScope ensures all child coroutines complete before it resumes, and its job is linked to the parent coroutine—so runBlocking will properly wait for everything to finish.

Solution 2: Replace async + await with withContext

Since you're just executing a single suspending function on a different dispatcher and waiting for the result, withContext is more concise and avoids unnecessary async overhead:

private suspend fun suspendingFunction(no: Int): Result<Int> {
    val plusOne = withContext(Dispatchers.Default) {
        delayedPlusOne(no)
    }
    return Result.success(plusOne)
}

withContext automatically uses the current coroutine's scope, so it's fully integrated with runBlocking's lifecycle.

Why This Works

By using either coroutineScope or withContext, you ensure that your child coroutines are part of the same coroutine hierarchy as the runBlocking coroutine. This means runBlocking will properly wait for all child operations to complete before resuming, eliminating the CoroutineSingletons type cast error.

After making either change, your runBlocking code will behave identically to the suspend main function, printing 3 as expected.

内容的提问来源于stack exchange,提问作者Abdul Kader Jeelani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 16:02:46