Satyam Agarwal
08/03/2020, 11:06 AMIO<A>
. I thought of starting with this line :
IO<A>
is just a another way of writing suspend () -> Either<Throwable, A>
is this true ? I grasped this from the PR Simon worked on to bring features from IO to Either using coroutines so that we don’t have to box our types which apparently is costly.
I know IO has its own thread pool, works on fibres and not coroutines, but I was wondering if the above statement could be a layman’s explanation ?simon.vergauwen
08/03/2020, 12:05 PMIO<A>
~> suspend () -> A
, since suspend
encapsulates Throwable
.
If you try to run suspend () -> A
, you always need to provide a function (Result<A>) -> Unit
in startCoroutine
when constructing a Continiuation<A>
.
Similarly to IO
which exposes a function unsafeRunAsync
which takes a function (Either<Throwable, A>) -> Unit
.
So it's clear these two are the same.
Building IO
is more expensive than using suspend
since IO
is build at runtime, and suspend
is generated by the compiler at compile time. This costs becomes neglectable in heavy concurrent applications with a lot of IO operations, and it becomes more expensive in loops for example.Satyam Agarwal
08/03/2020, 12:07 PMsimon.vergauwen
08/03/2020, 12:07 PMIO
module exposes a set of utilities like Fiber
, Resoure
, Schedule
, dispatchers. and much more to work with IO
.
In the last year I've been working hard on trying to figure out how we could translate all these same principles and patterns from IO
to suspend
without losing any features or capabilities. Which finally landed in the PR you probably saw already.
The same readme can be found here: https://github.com/arrow-kt/arrow-fx/tree/master/arrow-fx-coroutinessimon.vergauwen
08/03/2020, 12:08 PMSatyam Agarwal
08/03/2020, 12:12 PMsimon.vergauwen
08/03/2020, 12:14 PMSatyam Agarwal
08/03/2020, 12:15 PMfun Application.myModule() : Unit
to suspend fun <http://Application.my|Application.my> module() : Unit { ... myUnsafeIOWrapped.suspendedCancellable() }
Also he asked about blocking and non-blocking.simon.vergauwen
08/03/2020, 12:31 PMThread.sleep
which actually block a thread.
Which is problematic on a pool such as ComputationPool
, IO.dispatchers().default()
or Dispatchers.Default
, since those pools are "work-stealing". Meaning they interleave computational work, which is not possible if an operation is blocking the thread.
(TL;DR Blocking doesn't allow interleaving of tasks)
So a blocking task should be scheduled on an "IO" pool, which creates a new thread per operation. Hence blocking is not a problem, since no interleaving happens on this pool. IOPool
, IO.dispatchers().io()
, <http://Dispatchers.IO|Dispatchers.IO>
, etc.
The part about IO
and suspend
could be argued about perhaps, since on a back-end requests are running in parallel there is always asynchrony involved. What makes both IO
and suspend
so special is that they can wrap a callback
which regular code cannot.
Being able to wrap a callback allows you to jump to a different scheduler, and back, without having to write any of the orchestration around it like you normally would with callbacks.
val result: Result? = ...
runOnComputation { result ->
runOnIO(result) { res2 ->
runOnComputation(res) { res3 ->
finished, return result (???)
result = res3
}
}
}
use result??
compared to
val res = runOnComputation()
val res2 = runOnIO(res)
val res3 runOnComputation(res2)
Where you can implement runComputation
with suspendCoroutine
like you could do with IO.async
.simon.vergauwen
08/03/2020, 12:34 PMparTraverse
or parTupled
or run them synchronously depending on the config we passed to Ktor.
Similarly you can make functions uncancellable
by wrapping them in uncancellable { }
. Even if they contain cancellable code.Satyam Agarwal
08/03/2020, 12:39 PM