Any way to avoid the warning with `suspend fun Cor...
# coroutines
d
Any way to avoid the warning with
suspend fun CoroutineScope.startThing(action: Deferred<String>): String
🧵
Copy code
package com.stochastictinkr

import kotlinx.coroutines.*

fun CoroutineScope.launchAction(action: String): String {
    launch {
        delay(1000)
        println("$action completed")
    }
    return "$action started"
}

suspend fun startJobSuspend(action: Deferred<String>): String {
    val string = action.await()
    return coroutineScope { launchAction(string) }
}

suspend fun CoroutineScope.startJobSuspendScoped(action: Deferred<String>): String {
    val string = action.await()
    return launchAction(string)
}

fun CoroutineScope.startJobDeferred(action: Deferred<String>): Deferred<String> {
    return async {
        launchAction(action.await())
    }
}

fun main() = runBlocking {
    println("About to start main")
    val main = launchAction("main")
    println(main)

    println("About to start suspend-action")    
    val suspend = startJobSuspend(async { "suspend-action" })
    println(suspend)
    
    println("About to start suspend-scoped-action")
    val suspendScoped = startJobSuspendScoped(async { "suspend-scoped-action" })
    println(suspendScoped)
    
    println("About to start deferred-action")
    val deferred = startJobDeferred(async { "deferred-action" })
    println(deferred.await())
}
s
Why the need for a
CoroutineScope
receiver for the suspend version of
startThing
?
d
So that I can call the non-suspend version which needs it for launch
s
Within a
suspend
function, you can call the function
coroutineScope { /* this is a CoroutineScope*/... }
, in which the CoroutineScope of the calling function will be provided.
d
Hmm, that's not what the docs say. coroutineScope will suspend until all the child jobs are complete.
s
Correct. You want the
suspend
fun to resume before the Deferred 'await' call has finished?
d
I want the non-suspend startThing to return the string immediately.
s
(the code snippet is a bit hard to follow with all things called
startThing
🙂
d
Yeah, let me rewrite it.
s
Still, your
fun CoroutineScope.startThing(action: String): String
will return immediately. A call
coroutineScope { }
calling that function will only resume until that
launch
has finished.
d
I've edited the snippet above, and added example main.
s
You also want
startJobSuspendScoped
to possibly resume before the
launchAction
has finished?
d
I want it to resume before the
launch
within
launchAction
has finished..
The actual work is basically an event loop, not just a delay then return.
s
And you have the
startJobSuspendScoped
marked as
suspend
only so that you can call the
await
on the
action
?
d
Thats the main reason, yeah.
Hmm, even
startJobDeferred
doesn't do what I want. Only
startJobSuspendScoped
does what I expect.
s
And this is the output you want to see?:
Copy code
suspend-scoped-action started
suspend-scoped-action completed
d
Yes.
s
Your
startJobSuspendScoped
needs to suspend to await the action string, but should launch the action separately. This means they have separate lifecycles.
Copy code
suspend fun CoroutineScope.startJobSuspendScoped(action: Deferred<String>): String {
  val string = action.await()
  return launchAction(string)
}
will accomplish this, but you get the warning about it being both suspend and having a CoroutineScope.
d
Yes.
That warning is what prompted this thread.
s
This is not an error, but a warning, which you can choose to ignore, but it may be a bit unclear to the caller on how this function should behave. That is why the warning is shown. For more clear, although more verbose code, you should separate those two concerns (lifecycle differences).
Copy code
val result = launchAction(async { "suspend-scoped-action" }.await())
println(result)
Here, the
await
is called in the calling function and then the launchAction is done asynchronously.
d
Yeah, though the real use-case has one more layer of indirection that makes that impossible.
s
Ugh.... 🙂
Then I'd do the one that generates that warnign, but suppress that warning with a proper comment to explain it.
d
The real use-case receives a host-name, and then asynchronously resolves it.
s
Or wrap
launcAction
inside an object that has its own CoroutineScope
d
So I have basically 2 versions of connect:
CoroutineScope.connect(existingConnection: Connection)
and
CoroutineScope.connect(hostName: String)
Here's the real code:
Copy code
fun CoroutineScope.telnet(
    readWriteCloser: ReadWriteCloser,
    virtualTerminal: NetworkVirtualTerminal,
    options: suspend OptionsConfiguration.() -> Unit
): Telnet {
    val (reader, writer, closer) = readWriteCloser
    val output = TelnetOutput(writer)
    val optionNegotiator = OptionNegotiator(output)
    launch {
        optionNegotiator.options.options()
        TelnetInputProcessor(reader, optionNegotiator, virtualTerminal).processInput()
    }
    return Telnet(optionNegotiator, output, closer)
}
s
Copy code
class Action(private val scope: CoroutineScope = CoroutineScope(Job())) {    
    fun launch(action: String): String {
        scope.launch {
            delay(1000)
            println("$action completed")
        }
        return "$action started"
    }
}

suspend fun startJobSuspendUsingAction(action: Deferred<String>): String {
    val string = action.await()
    return Action().launch(string)
}
d
Hmm, that gave me an idea:
Copy code
suspend fun telnet(
    coroutineScope: CoroutineScope = CoroutineScope(Job()),
    remoteHost: String,
    remotePort: Int = 23,
    socketConfiguration: suspend SocketChannel.() -> Unit = {},
    virtualTerminal: NetworkVirtualTerminal,
    options: suspend OptionsConfiguration.() -> Unit = {},
): Telnet {
    SelectorProvider.provider()
    val socket = SocketChannel.open().apply {
        configureBlocking(false)
        socketConfiguration()
        awaitConnect(inetSocketAddress(remoteHost, remotePort))
    }
    return coroutineScope.telnet(virtualTerminal, socket, options)
}