协程有趣行为的解释

Explanation of intriguing behavior of coroutines

我有一个方法可以连续 运行s 一些操作。 Is 实际上是一个 for 循环,它将其内容循环 50 次,每次迭代大约需要 0.2 秒。在此执行期间,我在屏幕上显示了一个恒定的动画。所以,很明显我希望将这些操作从主线程中移除,这样我的动画就可以跟上(或者可以进行重组,这就是 Compose)。我意识到,这个简单的方法

fun run(){
        repeat(10000) {
            repeat(5000){
                print("I ♥ Kotlin")
            }
        }
    }

如果 运行 在标准的可组合范围内,就像人们期望的那样,将阻塞 UI 线程。

b) 如果我在 LaunchedEffect 中调用它,同时将它嵌套在对 launch{...}.

的调用中,它也会阻塞 UI 线程

c) 如果我在 I/O 协程上 运行 它不会阻塞,这也是默认协程。

d) 如果 运行 在 Main Dispatcher

上,应用程序有时会崩溃

现在,简单的问题 - 这是为什么?

LaunchedEffect(Unit){
 run() // Block
}
Launchedeffect(Unit){
 launch{
  run() // Block
 }
}
LaunchedEffect(Unit){
 withContext(Dispatchers.Main){
  run() //Blocks, and at times, crashes
 }
}
LaunchedEffect(Unit){
 withContext(Dispatchers.IO){
  run() // Runs without blocking
 }
}
thread{
 run() //Runs without blocking, no crash
}

谁能解释为什么 Dispatchers.IO 有效而其他无效?这有点给我带来不必要的压力。

如果有人需要快速动画UI来测试它,就在这里

@Composable
fun DUM_E_MARK_II() {

    val sizeTransition = rememberInfiniteTransition()

    val size by sizeTransition.animateFloat(
        initialValue = 50f,
        targetValue = 200f,
        animationSpec = infiniteRepeatable(
            keyframes { durationMillis = 1000 },
            repeatMode = RepeatMode.Reverse,
        )
    )

    Icon(
        imageVector = Icons.Filled.Warning,
        contentDescription = "",
        modifier = Modifier.size(size.dp),
        tint = Color.Red
    )

}

您的代码是 long-running、non-suspendable 任务。它会在整个生命周期内阻塞它运行的任何线程。当您阻塞 UI 线程时,它会导致 UI 冻结,并在超时后 Android 终止此类行为不当的应用程序。

如果您使用任何使用自己的线程池的调度程序,例如 IO,任务将阻塞 non-UI 个线程。

 withContext(Dispatchers.IO){
  run() // Runs without blocking
 }

在这里,你明确表示你想在另一个线程上 运行 这个,特别是一个不会对主线程产生影响的线程,所以当你调用:

withContext(Dispatchers.Main){
  run() //Blocks, and at times, crashes
 }

那么是的,这可能应该崩溃并出现ANR异常,因为主线程被阻塞的时间太长了,那就是the point of withContext指定哪里这项工作应该完成,密集或长时间 运行ning 任务不应在 Dispatchers.Main

This function uses dispatcher from the new context, shifting execution of the block into the different thread if a new dispatcher is specified, and back to the original dispatcher when it completes.

run()函数是一个long-running函数,它会阻塞执行它的线程。

让我们一一考虑每个案例:

  1. run() 函数在 Main(UI) 线程 中调用,阻塞它。

    LaunchedEffect(Unit) {
        run() // Block
    }
    
  2. run() 在协程内部调用,协程使用 launch 协程构建器启动。协程的上下文是组合的 CoroutineContext,我假设它由 Dispatchers.Main 调度程序组成。所以run函数也在Main(UI)线程中调用,阻塞了

    Launchedeffect(Unit) {
      launch {
       run() // Block
      }
    }
    

您可以使用 withContext(Dispatchers.IO) 使 run() 函数 suspend,它会将 run 函数的执行上下文切换到 Dispatchers.IO 线程池:

   suspend fun run() = withContext(Dispatchers.IO) {
       // this is executed in background thread
   }

   Launchedeffect(Unit) {
     run() // Not Blocking
   }

   Launchedeffect(Unit) {
     launch {
       run() // Not Blocking
     }
   }
  1. run() 函数在 Main(UI) thread 中调用,阻塞它,因为使用了 Dispatchers.Main用于其上下文执行。 Dispatchers.MainMain(UI) 线程.

    中执行协程
    LaunchedEffect(Unit){
      withContext(Dispatchers.Main){
        run() // Blocks, and at times, crashes
      }
    }
    
  2. 在这种情况下它运行时不会阻塞,因为 Dispatchers.IO 被用作协程上下文。它使用后台线程池。它不会阻塞主线程,因为它在后台线程中执行。

    LaunchedEffect(Unit){
      withContext(Dispatchers.IO){
       run() // Runs without blocking
      }
    }
    
  3. 这会在不阻塞主线程的情况下运行,因为另一个线程(后台线程)用于执行它。

    thread{
       run() //Runs without blocking, no crash
    }