A Flash Player minden futó példánya egy szálon fut, így az ActionScript nyelvben sincsenek közvetlen párhuzamosságot támogató eszközök. Ezt az egy szálat használja az AVM az időszalag animálására és renderelésére valamint bármilyen egyéb programozott logika futtatására is. Az időszalag animációt ráadásul független attól, hogy van-e rajta aktuális animáció, folyamatosan rendereli - lehetőleg a beállított framerate ütemében.
Alternatív eszközökkel azonban lehetséges korlátozott módon szimulálni aszinkron metódus hívásokat, elsősorban az ActionScript eseményterjesztési modelljét kihasználva.
Ciklusok
A legtriviálisabb módon pszeudo-párhuzamos viselkedést ciklusokkal lehet szimulálni, a ciklus végrehajtását preemptív módon felfüggesszük, kisebb szeletekre bontjuk. A ciklust egy időben folyamatosan végrehajtódó blokkban helyezzük el, pl egy ENTER_FRAME esemény handlerében. Így megszakítás után, a következő végrehajtási ciklusban automatikusan folytatódik az.
var loopIndex:int = 0; function enterFrame() { var i, n = myArray.length; for (i=loopIndex; i
A ciklusból való kilépés feltételét a mustExit() feltétel értékeli ki. Ez nyilván programtól függően változhat. Akár meghatározott iterációk után, idő elteltével a Timer osztályt használva. Valódi szálkezeléssel szemben a legnyilvánvalóbb hátránya ennek a megoldásnak, hogy nem a pihenési, hanem az aktív processzoridőt kell megbecsülni, anélkül hogy pontosan ismerhetnénk a program egészének a számításigényét.
Ennek a modellnek egy implementációja a CaesarCipher osztály stringek kódolására és dekódolására. Egy beágyazott objektumtól kapja az ENTER_FRAME eseményt, így nincs szükség semmilyen megjelenítési listához csatolni, se a Timer osztályt használni. A futás befejeltéről pedig egy Event.COMPLETE eseményt küld. A kódolás eredményét ugyan egy változóból lehet kiolvasni, könnyedén átalakítható hogy az esemény egy adattagján adja tovább.
Használata:
var text:String = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; var cipher:CaesarCipher = new CaesarCipher(text, 3); cipher.addEventListener(Event.COMPLETE, complete); cipher.run(); function complete(event:Event):void { trace("Result: " + cipher.result); // Result: Oruhp lsvxp groru vlw dphw, frqvhfwhwxu dglslvflqj holw. }
Idő-alapú kilépési feltétel
Ha adott diszkrét idő eltelte után akarjuk megszakítani a ciklust, a getTimer() metódust használhatjuk hozzá. Fontos azonban megjegyezni, hogy a Flash VM alatt még a rendszeridő sem tekinthető pontosnak, pont az egyszálúsága miatt. A megfelelő intervallum kiválasztása valamivel nehezebb mint egyszerűen n-db iteráció után kilépni.
var estimatedTime = 1000/fps - renderTime - otherScriptsTime; var startTime = 0; var loopIndex = 0; function enterFrame() { startTime = getTimer(); var i, n = MyArray.length; for (i=loopIndex; iestimatedTime){ loopIndex = i; return; } doLogic(MyArray[i]); } finish(); }
Valamivel csökkenthető az overhead, ha nem minden iterációban ellenőrizzük az ETA időt..
Beágyazott ciklusok
Mátrixok vagy egyéb több dimenziós tömbök bejárásához szintén alkalmazhatóak az eddigiek, csupán több állapotváltozót kell felvenni a megfelelő folytatáshoz
var loopIndexI:int = 0; var loopIndexJ:int = 0; function enterFrame() { var i, n = MyArray.length; for (i=loopIndexI; i
A RenderGradient osztály példa hasonlóan működik a CaesarCipher osztályhoz. Egyetlen különbség ami érinti a használati esetet, hogy nincs visszatérési értéke, a renderelés az átadott bittérképen történik meg, látványos animációt eredményezve ha a feldolgozás közben azt kirajzoljuk.
Használata:
var renderer:RenderGradient = new RenderGradient(bmp, 0xFF0000, 0x0000FF); renderer.run();
Szekvenciák
Az ECMASCRIPT örökségének köszönhetően szekvenciális programokat is könnyedén átalakíthatunk a fentebbi mintákra, hisz metódust is átadhatunk paraméterül. Az aszinkron programrészt megfelelően felszeletelve beszórjuk egy tömb elemeinek, és alkalmazzuk tetszőlegesen valamely korábbi megoldást.
function method1(){ } function method2(){ } function method3(){ } var seq:Array = [method1, method2, method3];
var selfDescription:XML = describeType(this); var methods:XMLList = selfDescription.method();
Bitmapekre alkalmazott különböző, egyedi metódusokban definiált szűröket láncol össze, a szűrőket egyesével egyszerre lefuttatja a teljes bittérképen, azonban egy képkockában csak egy szűrőt.
Használat:
var sequence:Array = [twirl, wave, bubble]; // custom functions var applicator:ApplyFilters = new ApplyFilters(); applicator.apply(bmp, sequence);
Rekurzív metódusok
Rekurzív függvényt úgy lehet aszinkronná tenni, hogy megszakítás - és folytatás - után, a hívási vermet újra felépíti úgy, hogy a korábban elvégzett hívások eredményeit megőrzi egy állapotváltozóban. Ez a változó jelzi, hogy a hívás adott szinten már kiértékelődött, és rögtön továbblép a rekurzióhoz.
var init:Boolean = false; function rec(){ if (mustExit()){ throw new Error; } if (!init){ doLogic(); init = true; } rec(); }
Mivel a hívási lista valamely szintjén lépkedünk, egyszerűbb return helyett egy kivételt dobni (amit a hívó kellően elfog), így egy utasítással visszapörgethetjük a hívási listát.
Mivel az init eredményét minden szinten külön kell tárolni, minden hívást egy osztály-példányba kell beágyazni. Ehhez valójában két külön osztály szükséges: egy, amelyik a hívást kezdeményezi, és egy másik, ahol a rekurzív metódus van.
A faktoriális számítás aszinkron példája. A Factorial osztály a hívó, és egy segéd-osztályban történik a valódi rekurzió.
Használat:
var fact:Factorial = new Factorial(8); fact.addEventListener(Event.COMPLETE, complete); fact.run(); function complete(event:Event):void { trace(fact.result); // 40320 }
Minden hívási szinten egy új példány jön létre a segédosztályból, ami állapota alapján megszakítás esetén a függvény tudja folytatni a megszakítási ponttól.
A gyorsrendező algoritmus aszinkron implementációja
Használata:
var array:Array = [2,4,7,9,1,9,8,5,3,0,3,5,6,7,8,8,1,2,0,3,7,5,5,9]; var qsort:Quicksort = new Quicksort(array); qsort.addEventListener(Event.COMPLETE, complete); qsort.run(); function complete(event:Event):void { trace(array); //0,0,1,1,2,2,3,3,3,4,5,5,5,5,6,7,7,7,8,8,8,9,9,9 }
A faktoriálissal ellentétben itt az eredményt a paraméterként adott tömbben adja vissza
Alternatív megoldás az objektum-szintű rekurzióra, ha a feldolgozás tényét/eredményét egy listában tároljuk.var init:Array = new Array(); var numCall:int = 0; function rec(numCall:int){ if (mustExit()){ throw new Error(); } if (!init[numCall]){ init[numCall] = true; doLogic(); } rec(++numCall); }
Így minden szinten tudjuk, hogy éppen hol járunk, és adott szinten jártunk-e már. A végeredménye hasonló az objektum-szintű rekurzióhoz, de lényegesen kisebb overheaddel, hiszen nem kell minden híváshoz példányosítani.