Threads in Actionscript 3
Every once in a while, someone decides they would like to see threading or background processing in Actionscript. While the underlying Flash Player and Operating System use threads, the execution model for Actionscript does not.
For those who don't know, 'threading' is essentially the ability to have the code do something in the background while the UI is doing something else. Because Actionscript is single-threaded, if you spend lots of time doing heavy computation, the UI cannot be updated while you're doing that computation so your application appears stuck or effects don't run smoothly.
Similarly, there is no yielding or blocking in Actionscript either. If the next line of code is supposed to run, you cannot prevent the next line of code from running. That means that when you call Alert.show(), the next line of code following that runs right away. In many other runtimes, the Alert window has to be closed before the next line of code continues.
Threading may be a feature of Actionscript some day, but until then, you have to live with the fact that there is no such thing right now.
In other single-threaded environments, you can get something like threads via 'pseudo-threading', which involves dividing up the heavy computation into small chunks on your own. (There is no solution to allow for blocking or yielding other than refactoring the application to be even-driven). Pseudo-threading is painful, but doable in most situations. Iterative tasks are easier than recursive ones, the chunk size has to be small enough to not degrade the UI response time and restoration of the execution context must be efficient otherwise you'll spend too much time saving and restoring your state and not get anything done.
Someone asked me how I would generalize such a thing. I put this together in a couple of hours. It represents my first thoughts, not some deep thinking on the subject. A PseudoThread instance takes a callback function and a context object and calls the callback function with the context object as mahy times as it thinks it can get away with it within the frame interval. The callback function should update the context object before returning and return false when done and the PseudoThread instance dispatches an event and destroys itself.
The following is the a test that loads an image, and adds a red tint to a copy of the image one row at a time. I purposefully slowed it down with trace statements so you can see that it doesn't do it all at once and doesn't seem to affect the rotating button.
These two links are the source file and the test image.
PseudoThreadTest.mxml
the test image
The usual caveats apply (there are probably bugs, limitations, etc). Hope it helps.
Comments
Hi Alex,
I'd just come to requiring something like this in a project Im working on that uses JPEGEncoder to encode hi res images. This takes along time and I've been getting all the UI freezes you mention. My solution was to 'chunk' the encoding over frame intervals. So pretty much what you are talking about here.
I'll have a looko at PseudoThread later and maybe try to integrate it.
Thanks!
Matt
Posted by: mattjpoole | January 2, 2008 01:31 AM
Hello Alex,
I'm not sure your demo is woring right for me. I see the rotating button, but the image of the phone to the right is Red from the get go, so I don't see the one line at a time rendering of the red.
I'm running inside Firefox with the 9,0,115,0 flash player.
----------------------------------
It doesn't recolor in a way you can watch on screen. The image is colored in the background. The point is that if I didn't psuedo-thread, the delay between when you see the original image and the colored image would also cause the spinning button to stop spinning for roughly that duration.
Posted by: Todd | January 2, 2008 06:55 AM
I found a great way of doing this by delegating the task to the player/vm - its also less complicated and conforms to processor speed better. Inside an execute function:
var delay:Number = Math.round(Math.random()-0.45);
setTimeout(_execute, Math.round(delay);
Then call execute again from within _execute if need be.
By randomising the delay, you force the vm to sometimes create the calls in the next frame, allowing the ui to update smoothly. You can play with the -0.45 to control cpu usage/ computation speed.
It would be interesting to know if there was a way of working out dynamically what the constant would be, so that it performs consistently on every computer.
-----------------------
Interesting idea. I would worry that at the wrong frame you'd have several intervals to service and it would interrupt an animation in a noticeable way.
Posted by: Laurence Taylor | January 16, 2008 11:14 AM
Hi Alex,
I've used your PseudoThread.as and written a PseudoMutex that enforces ordering of code in different pseudo threads.
Take a look at this blog entry.
P.S: Your blog is pretty useful and cool. :)
Posted by: Anirudh Sasikumar | January 17, 2008 09:00 PM
Hi Alex,
Do you think we could use this concept to process updateDisplayList for example in DataGrid, and allow smooth movement of the scrollbars? I will try to implement it…
My DataGrid renders in 0.3 sec when it is full screen. So when dragging the scroll bars, it seems chunky...
Cheers,
Marcin
-------------
Threads won't help scrolling. You simply need to get the data on screen and you still have only one processor. Make sure your renderers are optimized. See the examples in other parts of this blog
Posted by: Marcin Glowacki | January 26, 2008 05:18 PM
As an actionscript newbie trying to code up a mandelbrot set plotter I hit this wall right away. Your blog gave me the hints I needed, although learning that there was no yeilding in a single threaded environment was a bit depressing..:(
Posted by: johnfredcee | March 25, 2008 07:46 AM