代码之家  ›  专栏  ›  技术社区  ›  Tiddo

使函数同时挂起和取消挂起

  •  1
  • Tiddo  · 技术社区  · 2 年前

    是否可以使函数“可选挂起”?为了举例说明,让我们使用这个函数来包装回调和前后日志:

    fun withLogs(cb: () -> Unit) {
      println("Before");
      cb();
      println("After");
    }
    

    现在我可以用普通函数调用这个函数,但不能用挂起的函数:

    fun unsuspend () {
        withLogs({ println("my func") }); // this is fine
    }
    
    suspend fun suspended() {
        withLogs(suspend { println("my func") }); // this is not
    }
    

    现在我可以为此创建一个显式重载,但除了几个suspend关键字外,它将完全相同:

    fun withLogs(cb: () -> Unit) {
      println("Before");
      cb();
      println("After");
    }
    
    suspend fun withLogs(cb: suspend () -> Unit) {
      println("Before");
      cb();
      println("After");
    }
    

    是否可以编写函数以复制cb的挂起状态?例如,这个想法:

    <S: suspend> fun withLogs(S cb: () -> Unit) { /* snip */ }
    
    fun unsuspend () {
        withLogs({ println("my func") }); // suspended
    }
    
    suspend fun suspended() {
        withLogs(suspend { println("my func") }); // unsuspended
    }
    
    

    到目前为止,我找到的最好的解决方法是使用 runBlocking 要从未挂起的版本委派到挂起版本,请执行以下操作:

    fun withLogs(cb: () -> Unit) = runBlocking(withLogs(suspend { cb() }))
    

    但希望有更优雅的东西

    1 回复  |  直到 2 年前
        1
  •  6
  •   Joffrey    2 年前

    如果像这里一样在适当的位置调用函数参数,那么最简单的方法(以及在大多数stdlib函数中如何执行)就是使函数 inline :

    inline fun withLogs(cb: () -> Unit) {
      println("Before")
      cb()
      println("After")
    }
    

    这样做是因为 cb 在这里内联,所以是否声明它并不重要 suspend 无论是否,其内容都将受到呼叫站点上的约束。

    如果您处于挂起上下文中,则可以在lambda中使用挂起函数调用:

    suspend fun caller() {
        withLogs {
            delay(100) // suspend is ok because we're in a suspend caller
            println("yeah!")
        }
    }
    

    在挂起上下文中:

    fun blockingCaller() {
        withLogs {
            delay(100) // compile error
        }
    }
    

    如果函数没有被调用(而是被传递),那么您将受到将回调传递给的其他函数的约束,并且您可能必须添加 关键字。这与其说是语言限制,不如说是概念上的限制。