Maybe dumb question. I currently need to expose a ...
# coroutines
e
Maybe dumb question. I currently need to expose a Kotlin class with methods' signatures like:
Copy code
fun doSomething(): CompletableFuture<T>
Internally I need to interface with suspending functions, is it considered correct to simply wrap the method content in
GlobalScope.future
?
Copy code
fun doSomething(): CompletableFuture<T> = GlobalScope.future {
  mySuspendingFunction()
  return ...
}
Should I also wrap it with a
withContext
?
s
This Slack needs Giphy integration so I can do a Boromir GIF captioned with “One does not simply call a suspending function” 😄😬
j
When you expose a future-based API like this, you should define what the lifetime of the coroutine that you launch should be. When do you stop being interested in the result? If the suspend function hangs forever, when do you want to cancel it? If someone calls
doSomething()
repeatedly, is it ok to have many coroutines fired? Basically, using
GlobalScope
means answering these questions by saying "I never want to cancel anything, let the coroutines leak forever if they hang". Using the
CoroutineScope.future
extension may be a good idea, but the choice of scope is the tricky part.
e
lol, but yeah you're right. I need some words of confirmation to understand if I'm heading to the right direction or not
k
Probably best to not use
GlobalScope
and instead tie a
CoroutineScope
to your class instance which holds this. Then expose a
close
or
cancel
function on this which cancels running tasks. Eg.
Copy code
class MyClass {
  private val scope = CoroutineScope(Job())

  public fun theFuture(): CompletableFuture<T> = scope.future {
    mySuspendingFunction()
  }

  private suspend fun mySuspendingFunction() { ... }

  public fun close() = scope.cancel()
}
@Joffrey beat me to it I couldn’t type fast enough 😂
e
Basically, using
GlobalScope
means answering these questions by saying "I never want to cancel anything, let the coroutines leak forever if they hang"
Wouldn't any error be propagated and handled by the
Future
itself? If something blocks processing inside of the coroutine, wouldn't it be exactly the same as if something blocks inside of a Future?
s
Can you describe a bit more about the use case? Why do you need to expose a
CompletableFuture
? What prevents you from exposing a
suspend
function instead?
e
@Sam I need interoperability with Java code (without messing with the usage sites).
j
Wouldn't any error be propagated and handled by the Future itself?
Errors, maybe. But I'm talking about cancellation here, and hanging work.
`Future`s are designed in a way that's independent from the work being done, just like coroutine's
Deferred
. It doesn't give the consumer any control over where the work is run. That's why future-based java APIs often come in conjunction with some way to configure an
ExecutorService
somewhere, to allow to control that work - cancellation (
close()
), threading/pool size, etc. On the other hand,
suspend
functions require consumers to define where to run them. That's why when you bridge futures to suspend functions, you have to define this (a scope for the lifetime, a dispatcher for the threading, etc.).
e
I would have thought that part was handled for me automatically inside of the coroutine framework, but it seems I need to make it happen myself
j
Well it is handled by the coroutines framework (there are defaults for thread pools with
Dispatchers.Default
or
<http://Dispatchers.IO|Dispatchers.IO>
), but when to cancel your work is something you have to decide yourself. The framework just provides you with the tool (
CoroutineScope
) to express it with little code. But where you create the scope and where you cancel it is up to you.
e
Thanks for the hints. I've wrapped a platform-specific
CoroutineScope
per each platform I support, and then use that expect declaration on my platform-agnostic service where I need to expose the results as
Future
and
Promise