用悬挂的lambda调用非插图的库功能

发布于 2025-02-08 06:30:35 字数 2545 浏览 0 评论 0原文

我正在调用一个非插入的库功能,提供我自己的功能f需要暂停,因为它从频道接收。当库函数称为时,我只想在当前上下文中等待它完成。库函数将始终在其正文中调用f(具体来说,库函数确实pref,然后发布,其中f必须在pre> pre>post之间调用,因此它可能是一个嵌套的函数)。但是,在f内部不再适用外部Coroutine上下文。

我的第一个想法是用runblocking将悬挂电话包围,但这会导致僵局,因为现在(潜在的)线程被阻止,直到接收>接收完成,从而阻止了生产者从进步。

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

// Some non-inlined library function that calls given function f immediately with some library provided value
fun libraryFunction(f: (Int) -> Int) =
    f(5)

fun main() {
    // Some outer loop that may sometimes only have 1 thread
    runBlocking(newSingleThreadContext("thread")) {
        // An existing channel
        val channel = produce {
            send("whatwewant")
        }
        // Our code
        libraryFunction { libraryProvidedValue ->
            println(libraryProvidedValue)
            runBlocking {
                println(channel.receive())
            }
            // A return value the library needs
            7
        }
    }
}

解决此问题的最佳方法是什么?可以防止使用内部runblocking吗?

换句话说:无论如何,在lambda功能中,我们仍然在同一coroutine之内吗?


作为附加说明,以下工作:

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

// Some non-inlined library function that calls given function f immediately with some library provided value
fun libraryFunction(f: (Int) -> Int) =
    f(5)

fun main() {
    // Some outer loop that may sometimes only have 1 thread
    runBlocking(newSingleThreadContext("thread")) {
        // An existing channel
        val channel = produce {
            send("whatwewant")
        }
        // <inline the start of the body of libraryFunction here> (which gives libraryProvidedValue)
        // Our code
        println(libraryProvidedValue)
        println(channel.receive())
        // <inline the end of the body of libraryFunction here>
    }
}

Update :在这种情况下,唯一的实际问题似乎是编译器在Lambda功能主体中不知道周围的Coroutine是相同的,而是如果确实如此,代码可以成功运行(可以通过插入库函数来看到)。从本质上讲,该代码的流程没有错(因为库函数称为其体内的lambda功能),但由于缺乏编译器可以确定的保证,这是一个缺点。 runblocking使编译器感到高兴,但它具有不必要的副作用(尤其是嵌套的阻塞部分,这使得由于外部Runblocking,与外部的通信变得困难只有线程)。

因此,我决定以使用延期的样式来重写我的整个代码,并在顶级等待等待,而不是暂停功能。我将其视为非常不合情的Kotlin-IC,它带有其本身的潜在问题(例如资源泄漏),但它适用于我的情况。

请注意,这仍然没有以任何方式回答提出的问题,但我想将其视为替代决定,以使面临类似问题的未来用户做出。

I am calling a non-inlined library function, providing my own function f that needs to suspend because it receives from a channel. When the library function is called I simply want to wait for it to complete in the current context. The library function will always call f within its body (specifically, the library function does pre, f, then post, where f must be called between pre and post, so it could have been an inlined function). However within f the outer coroutine context no longer applies.

My first thought was to surround the suspending call with runBlocking, but this causes a deadlock, because the (potentially single) thread is now blocked until receive completes, which prevents the producer from progressing.

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

// Some non-inlined library function that calls given function f immediately with some library provided value
fun libraryFunction(f: (Int) -> Int) =
    f(5)

fun main() {
    // Some outer loop that may sometimes only have 1 thread
    runBlocking(newSingleThreadContext("thread")) {
        // An existing channel
        val channel = produce {
            send("whatwewant")
        }
        // Our code
        libraryFunction { libraryProvidedValue ->
            println(libraryProvidedValue)
            runBlocking {
                println(channel.receive())
            }
            // A return value the library needs
            7
        }
    }
}

What is the best way to solve this issue? Can using the inner runBlocking be prevented?

In other words: is there anyway to pinky-promise that we are, in fact, still within the same coroutine in the lambda function?


As an additional illustration, the following works:

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

// Some non-inlined library function that calls given function f immediately with some library provided value
fun libraryFunction(f: (Int) -> Int) =
    f(5)

fun main() {
    // Some outer loop that may sometimes only have 1 thread
    runBlocking(newSingleThreadContext("thread")) {
        // An existing channel
        val channel = produce {
            send("whatwewant")
        }
        // <inline the start of the body of libraryFunction here> (which gives libraryProvidedValue)
        // Our code
        println(libraryProvidedValue)
        println(channel.receive())
        // <inline the end of the body of libraryFunction here>
    }
}

Update: In this case, the only real issue seems to be that the compiler is not aware of the surrounding coroutine being the same in the lambda function body - but the code is entirely possible to run successfully if it did (as can be seen by inlining the library function). In essence, there is nothing wrong with the flow of this code in particular (because the library function calls the lambda function within its body), but it is a shortcoming in the lack of guarantees the compiler can determine. While runBlocking makes the compiler happy, it has unwanted side effects (notably, the nested blocking part, which makes communication with the outside difficult due to the outer runBlocking blocking up the potentially only thread).

Because of this, I decided to rewrite my entire code surrounding this in a style that uses Deferred with an await at the top level, instead of suspend functions. I would regard this as very un-kotlin-ic, and it comes with potential problems of its own (like resource leaks), but it works for my scenario.

Note that this still does not answer the question posed in any way, but I wanted to note it as an alternative decision to make for future users faced with a similar problem.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

暖阳 2025-02-15 06:30:35

不幸的是,只有一种方法可以实现您想要的东西 - 使用runblocking,但它具有如您所描述的后果。 暂停函数应从coroutine调用(在这种情况下为runblocking)或其他暂停函数。因此,要实现此目的而无需使用runblocking libraryFunction函数必须接受暂停 f function and be 暂停< /code>本身:

suspend fun libraryFunction(f: suspend (Int) -> Int) = f(5)

我建议首先从频道接收值,然后调用libraryFunction

runBlocking(newSingleThreadContext("thread")) {
    // An existing channel
    val channel = produce {
        send("whatwewant")
    }
    // Our code
    val receivedValue = channel.receive()
    libraryFunction { libraryProvidedValue ->
        println(libraryProvidedValue)
        println(receivedValue)
        // A return value the library needs
        7
    }
}

Unfortunately there is only one way to achieve what you want - to use runBlocking, but it has consequences like you described. suspend functions should be called from a coroutine (runBlocking in this case) or another suspend function. So to achieve this without using runBlocking libraryFunction function must accept a suspend f function and be suspend itself:

suspend fun libraryFunction(f: suspend (Int) -> Int) = f(5)

I would suggest first to receive the value from the channel, and then call the libraryFunction:

runBlocking(newSingleThreadContext("thread")) {
    // An existing channel
    val channel = produce {
        send("whatwewant")
    }
    // Our code
    val receivedValue = channel.receive()
    libraryFunction { libraryProvidedValue ->
        println(libraryProvidedValue)
        println(receivedValue)
        // A return value the library needs
        7
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文