Home > database >  How to "fire and forget" with kotlin coroutines in a blocking event?
How to "fire and forget" with kotlin coroutines in a blocking event?

Time:10-07

I am making something similar to minecraft plugin, but the API is all in Java.

At some point, there are events. Every event is fired sequentially, and the API will wait until an event finishes before calling the event listener for the next event. These events are called from the main thread, this means that nothing else is running while your event listener code is being run, and the application will crash eventually if you are blocking the main thread for too long.

Basically, I want that when a player is opening an inventory, some first content is sent directly, and some second content is retrieved from a database. In Java, you, would do something like this:

private static final List<Item> firstContent = ...;

public static void onInventoryOpenEvent(Inventory inventory) {
    inventory.addItems(firstContent);
    forkJoinPool.submit(() -> { // "fire and forget"
        List<Item> secondContent = retrieveContentFromDB(); // long and blocking
        inventory.addItems(secondContent);
    });
}

And in kotlin, some would say to use the GlobalScope, others would say to not use it, but for now I don't see any other way than using it like this:

fun onInventoryOpenEvent(inventory: Inventory) { // not a suspending function
    inventory.addItems(firstContent)
    GlobalScope.launch {
        val secondContent = retrieveContentFromDB() // long and blocking
        inventory.addItems(secondContent)
    }
}

CodePudding user response:

If you truly want to fire and forget, such that the coroutine will not be cancelled under any circumstance, GlobalScope is the right way to do it. The need to truly fire and forget is rare enough in practice that Kotlin gives you a warning about a "fragile API" if you use it, I think because so many newbies were using it for everything. This was especially a problem on Android (where Kotlin is primarily used), where long running tasks that should run even off-screen should be handled in services instead of coroutines.

But, referring to your example, if inventory is something that could become obsolete and you want to free it to the GC at some point, you should use your own created CoroutineScope that you store in a property, so you can cancel it to clear any running coroutines and prevent them from hanging onto references that should be freed for the GC. If the scope might be used for more than one coroutine, then you should give it a SupervisorJob().

private val coroutineScope = CoroutineScope(SupervisorJob())

fun onEndOfLifecycle() { // some function called when cleaning up memory 
    coroutineScope.cancel()
}
  • Related