Why does the first code snippet produces Inappropriate blocking method call warning but not the second one?
private fun prepareList() = launch {
withContext(Dispatchers.IO) {
requireContext().openFileOutput(listFileName, Application.MODE_PRIVATE).use { out ->
requireContext().assets.open(listFileName).use {
it.copyTo(out)
}
}
}
}
private fun prepareList() = launch(Dispatchers.IO) {
requireContext().openFileOutput(listFileName, Application.MODE_PRIVATE).use { out ->
requireContext().assets.open(listFileName).use {
it.copyTo(out)
}
}
}
Related
As the title implies, I am curious if there's any difference between doing this;
fun main() {
val job = GlobalScope.launch(Dispatchers.Main) {
withTimeout(2000L) {
delayMe()
}
}
job.invokeOnCompletion { cause -> println("We were canceled due to $cause") }
}
suspend fun delayMe() {
withContext(Dispatchers.Default) {
delay(5000L)
}
}
or this;
fun main() {
GlobalScope.launch(Dispatchers.Main) {
try {
withTimeout(2000L) {
delayMe()
}
} catch(cause: Exception){
println("We were canceled due to $cause")
}
}
...
}
...
in terms of handling exceptions inside coroutines.
PS: The sample code above is inspired from here.
In the example: kotlin-examples/coroutines/src/main/kotlin/movierating/App.kt
There is the flowing code:
fun Route.coroutineHandler(fn: suspend (RoutingContext) -> Unit) {
handler { ctx ->
launch(ctx.vertx().dispatcher()) {
try {
fn(ctx)
} catch (e: Exception) {
ctx.fail(e)
}
}
}
}
In the latest kotlin-coroutine,to invoke launch must depend on a CoroutineScope;
So the launch can't be invoked in the extend function Route.coroutineHandler() ;
If always use GlobalScope.launch() to start couroutine,how manage the life-cycle properly?
So I use the flowing method:
interface SuspendHandler<E>: Handler<E>,CoroutineScope {
override fun handle(event: E) {
launch {
suspendHandle(event)
}
}
suspend fun suspendHandle(event: E)
}
fun <E> vertxSuspendHandler(vertx: Vertx = getDefaultVertx(),
block:suspend CoroutineScope.(E)->Unit): SuspendHandler<E>{
return object: SuspendHandler<E> {
override val coroutineContext: CoroutineContext
get() = vertx.dispatcher()
override suspend fun suspendHandle(event: E) {
block(event)
}
}
}
I don't know how use extend function in the latest coroutine api;
You can achieve that by adding the following extension:
fun Route.suspendHandler(requestHandler: suspend (RoutingContext) -> Unit) {
handler { ctx ->
CoroutineScope(ctx.vertx().dispatcher()).launch {
requestHandler(ctx)
}.invokeOnCompletion {
it?.run { ctx.fail(it) }
}
}
}
You can place this extension anywhere in the code.
I have been writing a test class:
class TestVerticle {
#BeforeEach
fun deploy_verticle(vertx: Vertx, testContext: VertxTestContext) {
vertx.deployVerticle(Verticle(), testContext.completing())
}
#Test
fun test(vertx: Vertx, testContext: VertxTestContext) {
testContext.verify {
GlobalScope.launch(vertx.dispatcher()) {
val reply = vertx.eventBus().requestAwait<Long>(AVIOEXTDMZAddr, "1")
assert(reply.body() == 1010L)
testContext.completeNow()
}
}
}
}
If the method start() of Verticle is written in the "common" way, the Test is passed positively:
override suspend fun start() {
vertx.eventBus().consumer<String>(AVIOEXTDMZAddr){
it.reply(1010L)
}
}
Differently, if I implement a different solution, with the use of vertx-lang-kotlin-coroutines API, the test throws a java.util.concurrent.TimeoutException
override suspend fun start() {
val consumerChannel = vertx.eventBus().consumer<String>(AVIOEXTDMZAddr).toChannel(vertx)
for (msg in consumerChannel) {
msg.reply(1010L)
}
}
what am I doing wrong?
Loop on channel blocks the coroutine. In this case, it blocks start of your verticle.
Wrap your for loop in launch block:
async {
for (msg in consumerChannel) {
msg.reply(1010L)
}
}
}
I thought I was familiar enough with Kotlin's coroutines, until I got this code.
1 to 8 are all printed except 2:
import kotlinx.coroutines.*
import java.lang.Runnable
import java.lang.Thread.sleep
import kotlin.concurrent.thread
fun main() {
runBlocking {
Client.createAccount()
delay(1000)
}
}
object Client: CoroutineScope {
override val coroutineContext = newSingleThreadContext("Client")
fun createAccount() = launch {
Client2.init(Runnable {
println('1')
launch {
println('2')
}
ok()
ok2()
})
println('7')
launch {
println('8')
}
}
fun ok() {
println('3')
launch {
println('4')
}
}
fun ok2() = launch {
println('5')
launch {
println('6')
}
}
}
object Client2 {
fun init(runnable: Runnable) = thread {
sleep(100)
runnable.run()
}
}
The result is:
7
8
1
3
4
5
6
The coroutine in callback will never be called. Why?
And if I remove the launch in createAccount() the 1 to 8 will be all printed.
Also if I use GlobalScope.launch { println('2') } instead of launch { println('2') }, I can also get the 2 printed.
the reason is that anonymous class uses its wrapper scope as a parent.
launch { println('2') } in Runnable { } will be cancelled when parent job createAccount() launched is completed.
Therefore, it can't be invoked because it would be cancelled right after launch { println('8') }.
So, If you change Client like below, it would print '2' correctly.
object Client: CoroutineScope {
override val coroutineContext = Dispatchers.Main
fun createAccount() = launch {
Client2.init(Run())
println("7")
launch {
println("8")
}
}
fun ok() {
println("3")
launch {
println("4")
}
}
fun ok2() = launch {
println("5")
launch {
println("6")
}
}
class Run: Runnable {
override fun run() {
println("1")
launch {
println("2")
}
ok()
ok2()
}
}
}
launch posts a Runnable in a Handler, so its code execution is not immediate.
launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) will immediately execute its lambda expression in the current thread.
change the dispatcher to the current one you are using
change the lunch from inside the thread to
launch (coroutineContext, CoroutineStart.UNDISPATCHED)
.
fun createAccount() = launch {
Client2.init(Runnable {
println('1')
launch (coroutineContext, CoroutineStart.UNDISPATCHED){
println('2')
}
ok()
ok2()
})
println('7')
launch {
println('8')
}
}
output:
7
8
1
2
3
4
5
6
I can't use "by lazy" because the callbacks require suspendCoroutine, which borks in android if it blocks the main thread, so I have to use the following "cache the result" pattern over and over. Is there a way to wrap it in a funButUseCachedResultsIfTheyAlreadyExist pattern to encapsulate the xCached object?
private var cameraDeviceCached: CameraDevice? = null
private suspend fun cameraDevice(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
... deep callbacks with cont.resume(camera) ...
}.also {
cameraDeviceCached = it
}
}
When what I'd really like to write is
private suspend fun cameraDevice(): CameraDevice = theMagicFunction { cont ->
... deep callbacks with cont.resume(camera) ...
}
You can build a generalized solution by wrapping an async call as follows:
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart.LAZY
class LazySuspendFun<out T>(
scope: CoroutineScope,
private val block: suspend () -> T
) {
private val deferred = scope.async(Dispatchers.Unconfined, LAZY) { block() }
suspend operator fun invoke() = deferred.await()
}
fun <T> CoroutineScope.lazySuspendFun(block: suspend () -> T) =
LazySuspendFun(this, block)
This is a simple example of how you can use it. Note that we are able to compose them so that we use a lazy-inited value as a dependency to getting another one:
val fetchToken = lazySuspendFun<String> {
suspendCoroutine { continuation ->
Thread {
info { "Fetching token" }
sleep(3000)
info { "Got token" }
continuation.resume("hodda_")
}.start()
}
}
val fetchPosts = lazySuspendFun<List<String>> {
val token = fetchToken()
suspendCoroutine { continuation ->
Thread {
info { "Fetching posts" }
sleep(3000)
info { "Got posts" }
continuation.resume(listOf("${token}post1", "${token}post2"))
}
}
}
On the calling side you must be inside some coroutine context so you can call the suspending functions:
myScope.launch {
val posts = fetchPosts()
...
}
This solution is robust enough that you can concurrently request the value several times and the initializer will run only once.
I'll write this as an answer, since it's not possible to post much code in comments.
What you're looking for is something like this:
private suspend fun cameraDevice() = theMagicFunction {
CameraDevice()
}()
suspend fun theMagicFunction(block: ()->CameraDevice): () -> CameraDevice {
var cameraDeviceCached: CameraDevice? = null
return fun(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
cont.resume(block())
}.also {
cameraDeviceCached = it
}
}
}
Unfortunately, this will not compile, since closures cannot be suspendable, and neither are local functions.
Best I can suggest, unless I miss a solution there, is to encapsulate this in a class, if this variable bothers you too much.