Marty Pitt
08/23/2021, 8:14 AMsuspend fun
Marty Pitt
08/23/2021, 8:14 AMsuspend fun ...
These calls will happen fairly frequently. The service call is defined as suspend becuase it has I/O. We can't really change either interfaces here.
In my code that will invoke the suspend
fun, I need to return a CompletableFuture containing my result, so I'm using the kotlinx-coroutines-jdk8
library with CoroutineScope.future { }
extension.
The hard part (for me) has been working out how to get a scope to call the .future method against. I don't want to use GlobalScope
, but I find the documentation confusing for how to handle this scenario.Marty Pitt
08/23/2021, 8:15 AM/**
* A coroutine scope that defers work back to the Unconfined dispatcher.
* This means that work that starts uses the calling thread until such time as it
* hits a suspension point, when it defers to that suspension code.
*
* Since the Transformation job is running in a thread from a CompletableFuture threadpool,
* we want to attach to the calling thread, until the code defers.
*
* This seems like a reasonable approach, but need validation.
*/
internal class TransformationScope : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Unconfined
}
Marty Pitt
08/23/2021, 8:15 AMMarty Pitt
08/23/2021, 8:16 AMval future: CompletableFuture<MyResult> = TransformationScope().future {
.. do work
}
Marty Pitt
08/23/2021, 8:17 AMNick Allen
08/24/2021, 2:54 AMGlobalScope.future(Dispatchers.Unconfined) { ... }
. So you are not helping yourself at all.
The reason GlobalScope
is frowned upon is that it's often used incorrectly. Usually work is done in the context of some larger group of work, and structured concurrency gives the benefits of being able to cancel all that work, fail the entire group if part fails, or wait for all that work to be done.
Structured concurrency only works if the CoroutineScope has a Job
in the context to represent that larger group of work. GlobalScope
does not have a Job
, not does your TransformationScope()
. So each launched coroutine is completely independent. Being completely independent is perfect for some cases, like a daemon coroutine that lives for the whole process, or if you need to manage the coroutine explicitly for some reason. But usually structured concurrency is what you really want because the coroutine is part of some larger task or lifecycle.
Your use case kinda seems like it may fall under the "manage the coroutine explicitly" case if the 3rd party library is in charge of the lifetime.
And yes, the new scope each time, is pointless. You could also go with an object like object TransformationScope : CoroutineScope by (GlobalScope + Dispatchers.Unconfined)
or just val transformationScope = GlobalScope + Dispatchers.Unconfined
Marty Pitt
08/24/2021, 3:45 PM