Bevezetés a Kotlin Coroutines-ba

1. Áttekintés

Ebben a cikkben a korlinokat vizsgáljuk a Kotlin nyelvén. Egyszerűen fogalmazva, a coroutines lehetővé teszi számunkra, hogy aszinkron programokat hozzunk létre nagyon folyékonyan, és ezek a Folytatás-átadás stílus programozás.

A Kotlin nyelv alapvető konstrukciókat ad nekünk, de a kotlinx-coroutines-mag könyvtár. Megnézzük ezt a könyvtárat, ha megértjük a Kotlin nyelv alapvető építőköveit.

2. Koroutin létrehozása BuildSequence

Hozzunk létre egy első korutint a buildSequence funkció.

És valósítsunk meg egy Fibonacci szekvenciagenerátort a következő funkcióval:

val fibonacciSeq = buildSequence {var a = 0 var b = 1 hozam (1), míg (true) {hozam (a + b) val tmp = a + b a = b b = tmp}}

A. Aláírása hozam funkció:

nyilvános absztrakt felfüggesztés szórakoztató hozam (értéke: T)

A függessze fel kulcsszó azt jelenti, hogy ez a funkció blokkolható. Az ilyen funkció felfüggesztheti a buildSequence coroutine.

A felfüggesztő függvények létrehozhatók standard Kotlin-függvényekként, de tisztában kell lennünk azzal, hogy ezeket csak egy coroutine-ból hívhatjuk meg. Ellenkező esetben fordítói hibát kapunk.

Ha felfüggesztettük a hívást a buildSequence, ez a hívás az államgépben dedikált állapotba alakul át. A coroutine átadható és hozzárendelhető egy változóhoz, mint bármely más függvény.

Ban,-ben fibonacciSeq coroutine, két felfüggesztési pontunk van. Először, amikor telefonálunk hozam (1) és másodszor, amikor telefonálunk hozam (a + b).

Ha az hozam függvény valamilyen blokkoló hívást eredményez, az aktuális szál nem fog blokkolni rajta. Képes lesz valamilyen más kód végrehajtására. Amint a felfüggesztett függvény befejezi a végrehajtását, a szál folytathatja a fibonacciSeq coroutine.

Tesztelhetjük a kódunkat, ha a Fibonacci szekvenciából néhány elemet veszünk:

val res = fibonacciSeq .take (5) .toList () assertEquals (res, listOf (1, 1, 2, 3, 5))

3. A Maven-függőség hozzáadása ehhez: kotlinx-coroutines

Nézzük meg a kotlinx-coroutines könyvtár, amely hasznos konstrukciókkal rendelkezik az alapvető koroutinok tetején.

Adjuk hozzá a függőséget a kotlinx-coroutines-mag könyvtár. Ne feledje, hogy hozzá kell adnunk a jcenter adattár:

 org.jetbrains.kotlinx kotlinx-coroutines-core 0.16 central //jcenter.bintray.com 

4. Aszinkron programozás a indítás () Coroutine

A kotlinx-coroutines A könyvtár sok hasznos konstrukciót ad hozzá, amelyek segítségével aszinkron programokat hozhatunk létre. Tegyük fel, hogy van egy drága számítási függvényünk, amely a Húr a bemeneti listához:

függessze fel a szórakozást drágaComputation (res: MutableList) {delay (1000L) res.add ("word!")}

Használhatjuk a dob coroutine, amely ezt a felfüggesztési funkciót nem blokkoló módon hajtja végre - érvként át kell adnunk egy szálkészletet.

A dob függvény visszatér a Munka példány, amelyen a csatlakozik() módszer az eredmények megvárására:

@Test fun givenAsyncCoroutine_whenStartIt_thenShouldExecuteItInTheAsyncWay () {// adott val res = mutableListOf () // when runBlocking {val ígéret = indítás (CommonPool) {drágaComputation (res)} res.add ("Hello") / ígéret.join ( / majd assertEquals (res, listOf ("Hello,", "word!"))}}

A kódunk teszteléséhez minden logikát átadunk a runBlocking coroutine - ami blokkoló hívás. Ezért a mi assertEquals () a szinkronban futtatható a kód belsejében runBlocking () módszer.

Vegye figyelembe, hogy ebben a példában, bár a dob() metódus indul el először, ez egy késleltetett számítás. A fő szál a - Hello - String az eredménylistára.

Az egy másodperc késleltetés után, amelyet a drágaComputation () funkció, a "szó!" Húr az eredményhez lesz csatolva.

5. A korutinok nagyon könnyűek

Képzeljünk el egy olyan helyzetet, amelyben 100000 műveletet akarunk aszinkron módon végrehajtani. Ilyen nagy számú szál ívása nagyon költséges lesz, és valószínűleg hozni fog OutOfMemoryException.

Szerencsére a koroutinok használatakor ez nem így van. Annyi blokkolási műveletet hajthatunk végre, amennyit csak akarunk. A motorháztető alatt ezeket a műveleteket rögzített számú szál fogja kezelni, anélkül, hogy túlzott szál létrehozása lenne:

@Test fun givenHugeAmountOfCoroutines_whenStartIt_thenShouldExecuteItWithoutOutOfMemory () {runBlocking {// megadott val számláló = AtomicInteger (0) val numberOfCoroutines = 100_000 // amikor val job = List (numberOfCoroutement) {launch (CommonPlace (CommonPlace}) {StartGold (CommonPlay} {(CommonPort}) {Start} (1000) job.forEach {it.join ()} //, majd assertEquals (counter.get (), numberOfCoroutines)}}

Ne feledje, hogy 100 000 koroutint hajtunk végre, és minden egyes futtatás jelentős késéssel jár. Ennek ellenére nincs szükség túl sok szál létrehozására, mert ezeket a műveleteket aszinkron módon hajtják végre a CommonPool.

6. Lemondás és időkorlátok

Néha, miután elindítottunk néhány régóta futó aszinkron számítást, törölni szeretnénk, mert már nem érdekel bennünket az eredmény.

Amikor aszinkron akciónkat a -val kezdjük dob() coroutine, megvizsgálhatjuk a aktív zászló. Ez a jelző értéke hamis, amikor a fő szál meghívja a megszünteti() metódus a Munka:

@Test fun givenCancellableJob_whenRequestForCancel_thenShouldQuit () {runBlocking {// megadott val job = launch (CommonPool) {while (isActive) {println ("dolgozik")}} delay (1300L) // amikor job.cancel () // majd törölje sikeresen}}

Ez egy nagyon elegáns és a törlési mechanizmus egyszerű használata. Az aszinkron műveletben csak azt kell ellenőriznünk, hogy a aktív zászló egyenlő hamis és törölje a feldolgozásunkat.

Amikor némi feldolgozást kérünk, és nem vagyunk biztosak abban, hogy a számítás mennyi időbe telik, akkor célszerű beállítani az ilyen művelet időkorlátját. Ha a feldolgozás az adott időkorláton belül nem fejeződik be, kivételt kapunk, és megfelelően reagálhatunk rá.

Például megismételhetjük a műveletet:

@Test (várható = CancellationException :: class) fun givenAsyncAction_whenDeclareTimeout_thenShouldFinishWhenTimedOut () {runBlocking {withTimeout (1300L) {repeat (1000) {i -> println ("Néhány drága számítási $ i ...") késés (500L)}} }

Ha nem határozunk meg időkorlátot, akkor lehetséges, hogy a szálunk örökre blokkolásra kerül, mert a számítás lefagy. Nem tudjuk kezelni ezt az esetet a kódunkban, ha az időkorlát nincs meghatározva.

7. Az aszinkron műveletek egyidejű futtatása

Tegyük fel, hogy két aszinkron akciót kell egyidejűleg elindítanunk, és utána várnunk kell eredményeikre. Ha a feldolgozásunk egy másodpercet vesz igénybe, és ezt a feldolgozást kétszer kell végrehajtanunk, a szinkron blokkolás végrehajtásának futási ideje két másodperc lesz.

Jobb lenne, ha mindkét műveletet külön szálakban futtathatnánk, és megvárnánk ezeket az eredményeket a fő szálban.

Kihasználhatjuk a aszinkron () coroutine ennek elérése érdekében két külön szál egyidejű feldolgozásával:

@Test fun givenHaveTwoExpensiveAction_whenExecuteThemAsync_thenTheyShouldRunConcurrently () {runBlocking {val delay = 1000L val time = MeasureTimeMillis {// adott val one = async (CommonPool) {someExpensiveComputool as some (delay)} two runBlocking {one.await () two.await ()}} // majd assertTrue (idő <késleltetés * 2)}}

Miután elküldtük a két drága számítást, a program végrehajtásával felfüggesztjük a programot runBlocking () hívás. Miután eredményeket egy és kettő rendelkezésre állnak, a korutin folytatódik, és az eredményeket visszaküldik. Két ilyen feladat végrehajtása körülbelül egy másodpercet vesz igénybe.

Áthaladhatunk CoroutineStart.LAZY mint a második érv a aszinkron () metódus, de ez azt jelenti, hogy az aszinkron számítást csak kérésig lehet elindítani. Mivel számítást kérünk a runBlocking coroutine, ez a hívást jelenti two.await () csak egyszer készül el one.await () befejeződött:

@Test fun givenTwoExpensiveAction_whenExecuteThemLazy_thenTheyShouldNotConcurrently () {runBlocking {val delay = 1000L val time = measureTimeMillis {// megadott val one = async (CommonPool, CoroutineStart.LAZY) {someExpensiveCompation (néhány = ExpensiveCompation someExpensiveComputation (delay)} // runBlocking {one.await () two.await ()}}} // majd assertTrue (idő> késleltetés * 2)}}

A végrehajtás lustasága ebben a példában a kódunk szinkron futtatását okozza. Ez azért történik, mert amikor hívunk várják(), a fő szál le van tiltva, és csak a feladat után egy befejezi a feladatot kettő kiváltásra kerül.

Tisztában kell lennünk azzal, hogy az aszinkron műveleteket lustán hajtsuk végre, mivel blokkoló módon futhatnak.

8. Következtetés

Ebben a cikkben a Kotlin-korutin alapjait néztük meg.

Ezt láttuk buildSequence minden korutin fő építőeleme. Leírtuk, hogyan néz ki a végrehajtás folyamata ebben a Folytatás-átadás programozási stílusban.

Végül megnéztük a kotlinx-coroutines könyvtár, amely sok nagyon hasznos konstrukciót szállít aszinkron programok létrehozásához.

Mindezen példák és kódrészletek megvalósítása megtalálható a GitHub projektben.


$config[zx-auto] not found$config[zx-overlay] not found