There is no better way for now. Currently, this a...
# coroutines
v
There is no better way for now. Currently, this a real issue with coroutines, one can easily forget to bind his jobs with Android activity, HTTP request lifecycle etc. and there is no way to enforce such binding. We have some thoughts about changing it: forbid top-level
async
and
launch
(and other top-level builders as well), define them as extensions on some parent job and provide an easy way to expose such parent job via android integration, new builders etc. The idea behind this approach is to make ownership of a job (actor, channel, cold stream, w/e) explicit in code, so cancellation will never be an issue. Then your example will be rewritten as
Copy code
class MyFragment : Fragment() {
  fun loadData() = lifecycle.job().launch(UI) {} // <- job() is extension on lifecycle, won't compile if one tries to write fun loadData() = launch(UI) {}
}
Why do we want to do it if it will require more effort to write concurrent code (and break a lot of existing examples)? Consider the following snippet:
Copy code
suspend fun computation() {
   val d1 = async {...}
   val d2 = async {...}
   return merge(d1.await(), d2.await())
}
If
d1
will throw an exception, no one will be able to cancel
d2
. And if
d2
throws another exception, no one will ever observe it! Though one may argue that it’s a programmer fault, this is how coroutines are usually recommended to be used (actually, it’s a simplified example from our guide). If
async
always should have a receiver,
computation
can be written only using some builder, e.g.:
Copy code
suspend fun computation() = job {
   val d1 = async {...} // implicit receiver on `job {}` 
   val d2 = async {...}
   return merge(d1.await(), d2.await())
} // <- here builder will cancel launched tasks if necessary
Now all launched tasks will be properly cancelled/awaited in the end of
job {}
and it’s not possible to write
computation
without any builder. To launch jobs from non-suspendable contexts we will provide something like
Eternal.async {}
, where
Eternal
is
object
, so it always will be imported explicitly and code becomes more self-documented: “this job is launched via Eternal, so it can’t be cancelled as part of parent-child hierarchy and either it’s a mistake or it’s a system-wide job”. Opinions about this idea (note: it’s only a draft, not even prototyped) are welcome