Corou%nesinKotlin
Thistalkcouldhavebeennamed…
• async/await/yield
• fibers
• [stackless]con%nua%ons
SuspendableComputa%ons
Outline• Mo%va%on/Examples• Solu%onsinotherlanguages• Kotlin’sSolu%on– Clientcode– Librarycode
• CompilingCorou%nes• Excep%onHandling• Appendix.SerializableCorou%nes?
“Legal”
• AllI’msayingisnomorefinalthanValhallaJ
valimage=loadImage(url)myUI.setImage(image)
Mo%va%onTime-consumingopera%on
UIThread
Latency!
• Blockingbad.Verybad.
asyncUI{valimage=await(loadImage(url))myUI.setImage(image)
}
SuspendableComputa%onSuspendingcall
Con%nua%on
Time-consumingopera%on
await(…) loadImage(url)
UIThread
WorkerThreadsetImage(…)
“CallbackHell”
CombiningFutures• CompletableFuture
.supplyAsync{loadImage(url)} .thenAccept(myUI::setImage)
• soveeeryfunc%onalJ
asyncUI{valimage=loadImageAsync(url)myUI.setImage(image)
}
FlexibilityAsynchronousopera%on
Con%nua%on
interfaceContinuation<P>{funresume(data:P)funresumeWithException(e:Throwable)}
<Image>
LibraryFunc%on(corou%nebuilder)
asyncUI{valimage=await(loadImage(url))myUI.setImage(image)
}
Run%meSupport
Con%nua%on
interfaceContinuation<P>{funresume(data:P)funresumeWithException(e:Throwable)}
<Image>
Summary:Goals• Asynchronousprograming(andmore)– withoutexplicitcallbacks– withoutexplicitFuturecombinators
• Maximumflexibilityforlibrarydesigners– withminimalrun%mesupport– andnomacrosJ
FlavorsofCorou%nes
Stackless Stackful
Languagerestric;ons UseinspecialcontextsL UseanywhereJ
Implementedin C#,Scala,Kotlin,… Quasar,Javaflow,…
Codetransforma;on Local(compilermagic)J AllovertheplaceL
Run;mesupport LidleJ Substan%alL
TheC#WayasyncTask<String>work(){
Thread.sleep(200);return“done”;
}asyncTaskmoreWork(){
Console.WriteLine(“Workstarted”);varstr=awaitwork();Console.WriteLine($“Workcompleted:{str}”);
}
Example:async/await(I)fun work(): CompletableFuture<String> = async { ! Thread.sleep(200) ! "done" !} !!fun moreWork() = async { ! println("Work started") ! val str = await(work()) ! println("Work completed: $str") !}
typeisop%onal
Example:async/await(I)fun work() = async { ! Thread.sleep(200) ! "done" !} !!fun moreWork() = async { ! println("Work started") ! val str = await(work()) ! println("Work completed: $str") !}
await()fun moreWork() = async { ! println("Work started") ! val str = await(work()) ! println("Work completed: $str") !} !!suspend fun <V> await(f: CompletableFuture<V>, c: Continuation<V>) { ! f.whenComplete { value, throwable -> ! if (throwable == null) ! c.resume(value) ! else ! c.resumeWithException(throwable) ! } !} !!!!!!!
async()fun moreWork() = async { ! println("Work started") ! val str = await(work()) ! println("Work completed: $str") !} !!fun <T> async( ! coroutine c: FutureController<T>.() -> Continuation<Unit> !): CompletableFuture<T> { ! val controller = FutureController<T>() ! c(controller).resume(Unit) ! return controller.future!} !
implicitreceiver λhasnoparams
Controller@AllowSuspendExtensions!class FutureController<T> { ! internal val future = CompletableFuture<T>() !! suspend fun <V> await(f: CompletableFuture<V>, c: Continuation<V>) { ! f.whenComplete { value, throwable -> !
if (throwable == null) ! c.resume(value) ! else ! c.resumeWithException(throwable) ! } ! } !! operator fun handleResult(value: T, c: Continuation<Nothing>) { ! future.complete(value) ! } !! operator fun handleException(t: Throwable, c: Continuation<Nothing>) { ! future.completeExceptionally(t) ! } !} !
fun work() = async { ! Thread.sleep(200) ! "done" !} !
Extensibilitysuspend fun <V> FutureController<*>.await( ! lf: ListenableFuture<V>, c: Continuation<V> !) { ! Futures.addCallback(lf, object : FutureCallback<V> { ! override fun onSuccess(value: V) { ! c.resume(value) ! } ! override fun onFailure(e: Throwable) { ! c.resumeWithException(throwable) ! } ! }) !} !!// Example !async { ! val res1 = await(getCompletableFuture()) ! val res2 = await(getListeableFuture()) !} !
Summary:Corou%neLibraries• funasync(coroutinec:…)
– func%onwithacoroutine parameter
• suspendfunawait(…,c:Continuation<…>)– func%onmarkedsuspend !– con%nua%onisimplicitlypassedinatthecallsite
• classController
– declaressuspend func%ons• mayallowsuspend extensions
– declaresreturn/excep%onhandlers
HowSuspensionWorksfun moreWork() = async { ! println("Work started") ! val str = await(work()) ! println("Work completed: $str") !} !!!!!!!!!!. !
controller.await( ! work(), ! current_continuation!) !return !
Yield(TheC#Way)IEnumerable<int>Fibonacci(){varcur=1;varnext=1;yieldreturn1;while(true){yieldreturnnext;vartmp=cur+next;cur=next;next=tmp;}}
Infinite(lazy)sequenceofFibonaccinumbers
Example:LazyFibonaccival fibonacci: Sequence<Int> = generate { ! var cur = 1 ! var next = 1 !! yield(1) !! while (true) { ! yield(next) !! val tmp = cur + next ! cur = next ! next = tmp! } !} !!assertEquals("1, 1, 2, 3, 5", fibonacci.take(5).joinToString())
fun <T> generate( ! coroutine c: GeneratorController<T>.() -> Continuation<Unit> !): Sequence<T> = ! object : Sequence<T> { ! override fun iterator(): Iterator<T> { ! val iterator = GeneratorController<T>() ! iterator.setNextStep(c(iterator)) ! return iterator ! } ! } !!class GeneratorController<T> : AbstractIterator<T>() { ! ... ! suspend fun yield(value: T, c: Continuation<Unit>) { ! setNext(value) ! setNextStep(c) ! } ! ... !}
CompilingtoStateMachinesgenerate { ! var cur = 1 ! var next = 1 !! yield(1) !! while (true) { ! yield(next) !! val tmp = cur + next ! cur = next ! next = tmp! } !}
L0
L1
L2
var cur = 1 !var next = 1
true
val tmp = cur + next !cur = next !next = tmp!
val fibonacci = generate { ! var cur = 1 ! var next = 1 !! yield(1) !! while (true) { ! yield(next) !! val tmp = cur + next ! cur = next ! next = tmp! } !}
CompilingCorou%nes(I)class fibonacci$1 implements Function1, ! Continuation { !volatileGeneratorControllercontrollervolatileintlabel=-2volatileintcurvolatileintnextpublicContinuation<Unit>invoke(GeneratorControllercontroller)publicvoidresume(Objectparam)publicvoidresumeWithException(Throwablee)privatevoiddoResume(Objectparam,Throwablee) }
forsharedlocalvariables
CompilingCorou%nes(II)• Fields:
– GeneratorControllercontroller– intlabel
• voiddoResume(Objectparam,Throwablee)– tableswitch(label)
case0:L0 case1:L1 case2:L2
– L0: ... label=1 controller.yield(1,/*continuation=*/this) return
– L1: ... label=2 controller.yield(next,/*continuation=*/this) return
– L2: ...
L0
L1
L2
var cur = 1 !var next = 1
true
val tmp = cur + next !cur = next !next = tmp!
CompilingCorou%nes(III)– L0:
varcur=1 varnext=1
this.cur=cur this.next=next
this.label=1 this.controller.yield(1,this) return
L0
L1
L2
var cur = 1 !var next = 1
CompilingCorou%nes(IV)• voiddoResume(Objectparam,Throwablee)
– L1: cur=this.cur next=this.next
if(e!=null)throwe
//while(true){
this.cur=cur this.next=next
this.label=2 this.controller.yield(next,this) return
L0
L1
L2
true
Summary:CompilingCorou%nes
• Note:generate()/yield()canbeexpressed– flexibility:þ
• Corou%nebodyiscompiledtoastatemachine• Onlyoneinstanceisallocatedatrun%me
asyncUI{valimage=await(loadImage(url))myUI.setImage(image)
}
IOExcep%on
CannotAwaitExcep%on
WindowDisposedExcep%on
Excep%onHandling
ThrowingandCatching• Whocanthrow– Synchronouscode(insideacorou%ne)– Asynchronousopera%ons(calledfromcorou%ne)– Librarycode(thatmanagesthecorouitne)
• Whocancatch– Thecorou%neitself(usercode)– Librarycode
Controller.handleExcep%on(e)voiddoResume(Objectparam,Throwablee)
tableswitch(label)case0:L0case1:L1case2:L2
try{ L0:
... label=1 controller.await(...,/*continuation=*/this) return
L1: ... label=2 controller.await(...,/*continuation=*/this) return
L2: ...
}catch(Throwablee){ controller.handleException(e)
}
Rou%ngAsynchronousExcep%ons• voiddoResume(Objectparam,Throwablee)...
– L1: //fields->locals
if(e!=null)throwe
...
//locals->fields
this.label=2 this.controller.yield(next,this) return
suspend fun await(f, c) { ! f.whenComplete { value, e ->! if (throwable == null) ! c.resume(value) ! else ! c.resumeWithException(e)
Example:Excep%onHandlingasyncUI{valimage=try{await(loadImage(url))}catch(e:Exception){log(e)throwe}myUI.setImage(image)}
Operationorder:• loadImage(url)
• ->tmp_future• ->actual_work()
• await(tmp_future)• <suspend>• actual_work()completes• <resume>• myUI.setImage(image)
actual_work(url)workerthread
Summary:Excep%onHandling• Uniformtreatmentofallexcep%ons– bothsyncandasync
• Defaulthandler:controller.handleException(e)
• Notcoveredinthistalk– Suspendinginfinallyblocks– CallingfinallyblocksthroughContinuation<T>
Appendix.SerializableCorou%nes?serializableAsync(getDB()){valnewUser=showRegistrationForm()sendConfirmationEmail(newUser)if(awaitEmailAddressConfirmation(newUser)){//registrationconfirmedintimeconfirmRegistration(getDB(),newUser)showWelcomeScreen(newUser)}else{//registrationnotconfirmedcancelRegistration(getDB(),newUser)}}
SuspendingCalls
Datatobeserialized:• label(stateoftheSM)• newUser
References• LanguageDesignProposal(KEEP)– hdps://github.com/Kotlin/kotlin-corou%nes– GiveyourfeedbackinGitHubIssues
• Examplelibraries– hdps://github.com/Kotlin/kotlinx.corou%nes