- 浏览: 1084997 次
- 性别:
- 来自: 杭州
文章分类
- 全部博客 (695)
- 心情日记 (14)
- AS开发工具 (12)
- 文章转载 (99)
- AIR (5)
- 问题总结 (46)
- SWF格式 (7)
- 测试总结 (10)
- 外文资料 (9)
- 算法技术 (33)
- AS3常用开源库 (43)
- 源码范例 (102)
- FLEX (72)
- FLASH 优化 (33)
- 游戏开发 (49)
- 开发技术 (11)
- 工作应用 (34)
- AS3收集 (140)
- WebBase (0)
- 开发构想 (4)
- 设计模式 (2)
- 框架和框架范例 (19)
- RED5 (3)
- java开发 (3)
- JAVA (1)
- FLASH-3D (23)
- 3D (6)
- 书籍 (10)
- 业界信息资料 (3)
- C# (1)
- JavaScript (12)
- HTML5 (6)
- Flixel (1)
- D5Power RPG网页游戏引擎 (0)
- ColorMatrixFilter - 获得相应颜色的色调 函数 (0)
- Starling (0)
最新评论
-
老顽童203:
字体
水果忍者鼠标跟随特效制作[转载] -
hairball00:
[转] 放出超多的Flash组件源代码 -
he74552775:
flash AS3 RegExp简单功能用法(转) -
hanshuai1232000:
第四点,有利也有弊,等你做了大型的aprg,你就知道了
[转]位图数据内存优化 -
yangfantao:
太感谢
[转] 放出超多的Flash组件源代码
地址:
http://www.senocular.com/flash/tutorials/asyncoperations/?page=2
Date September 19, 2009
Language ActionScript 3.0
Target Flash Player 9+
Introduction
In Flash Player, both the execution of ActionScript and screen rendering is handled in a single thread. In order for the screen to be rendered, all code execution must be completed. For a Flash Player SWF running at 24 frames per second, this allows all ActionScript operations run within a single frame (frame scripts, events, etc.), at most, around 42 milliseconds to complete - this not accounting for the amount of time necessary to perform the actual screen rendering which itself may vary. If the execution of a block of code requires more time, Flash Player will appear to lock up until it is finished, or, after a default timeout period, just stop executing code.
Script timeout error dialog (Debugger Player only)
This tutorial will cover solutions to this problem - when you have operations that contain ActionScript calculations requiring more time than that which is allotted within any given frame. Such calculations are needed to be executed asynchronously; they would be non-blocking and complete at some point in time after the code block which invoked them has completed its own execution. This would give the renderer a chance to draw one or more times before the calculation is complete preventing the player from locking up.
Requirements
A moderate understanding of ActionScript 3.0
A compiler for ActionScript 3.0 (i.e. Flash Professional, Flash Builder, Flex SDK)
Source files (Flash CS4 format; ActionScript externalized)
Table of Contents
Page 1
Introduction
Requirements
Concepts
Single-dimension Loops
Example: Caesar Cipher
Time-based Exit Conditions
Multi-dimension Loops
Example: Color Gradient
Handling for..in and for..each Loops
Page 2
Sequences
Array Sequencing
Linked List Sequencing
Example: Custom Bitmap Filters
Recursion
Example: Factorial
Example: Quicksort
Synchronous with Asynchronous
Example: Reading Inline and Remote Text
Conclusion
Concepts
A running Flash Player instance processes a constant loop of consecutive frames. Even if there is not necessarily a timeline to animate through, frames are still rendered in the sense that a single frame representing the SWFs contents is repeatedly processed. Each frame consists of what can broken down into 2 parts: the execution of ActionScript through the AVM or ActionScript Virtual Machine, and a visual rendering of the screen by the Flash Player renderer. Code execution consists of both running ActionScript as well as idle time leading up to the next render. ActionScript is arbitrarily executed based on actions and events that occur between screen renders so idle time and occurrences may vary.
Frames both execute ActionScript and render the screen
When idle time is eaten away by the execution of cpu-intensive ActionScript, the time between each frame render will take longer, and the playback frame rate will suffer. What was 1 frame executed in 42 milliseconds may instead take 62 milliseconds, or even more if the ActionScript requires it.
ActionScript taking a long time to complete delays rendering
If ActionScript could be processed in a different thread of execution, this could prevent the renderer from being blocked. This is, however, not possible, as Flash Player, by nature, uses only one thread for both code execution and the rendering of the screen. So to prevent processor-intensive ActionScript from blocking the renderer, its calculations must be broken up into smaller, individual segments, or chunks, that can be run individually across multiple frames. Between each segment, the renderer can redraw allowing playback to go uninterrupted.
ActionScript stretched across multiple frames
This division of a single block of executing code across multiple frames is often non-trivial. Such code needs not only to know when to stop, but how to get back to where it was when picking up from where it left off. This adds the dependency of persistent data. What was before a function block with local variables must now become an object with property values.
Single-dimension Loops
The simplest, and probably the most common, circumstance requiring code to be segmented into chunks is with looping. A long loop can easily take up a lot of processing time, especially if heavy calculations are being made within each iteration of that loop.
Standard for loops use an index variable to keep track of a location within a list of items to be handled during iteration. A standard array loop would look something like:
// pseudo code
var i, n = array.length;
for (i=0; i<n; i++){
process(array[i]);
}
Segmenting the loop so that it can be handled in multiple iterations would require breaking out of the loop in the middle of the iteration with the ability to return to it later starting with the value of the index i at the point in time when the break occurred. The retained value of i represents the persistent variable that needs to be saved so that it can be referred to in another frame.
// pseudo code
var savedIndex = 0;
var i, n = array.length;
for (i=savedIndex; i<n; i++){
if (needToExit()){
savedIndex = i;
break;
}
process(array[i]);
}
Since the point of segmenting is to allow frame rendering, the execution of each chunk is appropriately handled in an Event.ENTER_FRAME event. An event handler is set when the loop starts and would need to be removed when the loop is complete. The loop is complete when the loop finishes without being exited, which, in the conext of a function would be handled by return.
// pseudo code
var savedIndex = 0;
function enterFrame() {
var i, n = array.length;
for (i=savedIndex; i<n; i++){
if (needToExit()){
savedIndex = i;
return;
}
process(array[i]);
}
complete();
}
The determination for loop exiting is decided by needToExit()whose implementation is ultimately up to you. This can be based on a set number of iterations, or even based off of an elapsed period of time as determined by getTimer(). Ideally, the time allotted for execution would be just enough to fill all available idle time up until the next render. There is no way to know how much time that actually is, so approximations will no doubt be a part of this process.
Example: Caesar Cipher
This example puts to practical application the looping of the characters within a string divided over multiple frames. To keep things self-contained and organized, a class, CaesarCipher, will be used to retain the necessary persistent data for the loop, though this data could also be retained within the current working scope (such as in timeline variables in Flash authoring).
The CaesarCipher class applies a Caesar cipher to a string. A standard, synchronous function for this operation would appear as:
function caesarCipher(text:String, shift:int = 3):String {
var result:String = "";
var i:int;
var n:int = text.length;
for (i=0; i<n; i++){
var code:int = text.charCodeAt(i);
// shift capital letters
if (code >= 65 && code <= 90){
code = 65 + (code - 65 + shift) % 26;
}
// shift lowercase letters
if (code >= 97 && code <= 122){
code = 97 + (code - 97 + shift) % 26;
}
result += String.fromCharCode(code);
}
return result;
}
The CaesarCipher class uses this same logic but allows the operation to continue over multiple frames before it's considered complete. This process then becomes asynchronous. Certain things had to be considered when implementing this:
Only display objects receive Event.ENTER_FRAME events
Return values no longer apply to asynchronous operations
The goal of the CaesarCipher class is to processes data; instances of it are not meant to be displayed on the screen. As a result, it will not be based off of another DisplayObject class which keeps the Event.ENTER_FRAME event from being available. This dependency, however, can be easily satisfied with composition, including a simple DisplayObject class member such as a Shape instance within the CaesarCipher class definition. Even though that instance will never reach a display list, it will still dispatch the Event.ENTER_FRAME event that can be used by an instance of CaesarCipher.
Concerning return values, an approach used by existing implementations of asynchronous operations will need to be used. For example, consider the URLLoader class. Instances are used to load external content through an asynchronous operation started by the load() call. When complete, an Event.COMPLETE event is dispatched and the loaded data can be accessible through the data property of the URLLoader instance. The CaesarCipher class uses a similar implementation. To start the operation, a run() method is used. When complete, an Event.COMPLETE event will fire, and the would-be return value can be accessible through the result property.
The CaesarCipher class (available in source files)
Usage:
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.
}
View live CaesarCipher example.
Though this class retains its result data in a class member variable, it could also be passed passed to the event, for example, if using a TextEvent object for the complete event. This approach, however, would necessitate the creating of a new Event class to handle the results data, so it's usually easier to stick to a class variable.
Because this example is using a short string, and because a Caesar cipher is not especially difficult for ActionScript to pull off, an iteration counter was used to determine when the next frame should be allowed to render between processing steps. It may be more common that a time-based approach will be used to make sure you're getting the most out of what time you have to calculate results in your frame.
Time-based Exit Conditions
Exiting an asynchronous script based off of the time taken to process a calculation will likely be the best solution for handling the segmentation. For this, you would need to know how much time has passed since the current frame's calculations started, and how much time, per frame, the calculations are allowed to have.
//pseudo code
var allowedTime = 1000/fps - renderTime - otherScriptsTime;
var startTime = 0;
var savedIndex = 0;
function enterFrame() {
startTime = getTimer();
var i, n = array.length;
for (i=savedIndex; i<n; i++){
if (getTimer() - startTime > allowedTime){
savedIndex = i;
return;
}
process(array[i]);
}
complete();
}
The times for rendering and other scripts will be estimated, but the more accurate they are, the better use of the available frame time you'll get. Note that these values can change depending on processor speed of computer running the script which can make them variable even at runtime. The closer it is to filling up the time available in the frame the better. You may even want to overshoot a little bit to make processing go as fast as possible at the expense of the frame rate depending on what's being presented to the user when the operation is being run.
For operations that contain a lot of looping with smaller calculations, you may not want the exit condition to be checked in every loop iteration - this especially when using getTimer() since it will help reduce the overhead of calling the function and looking up that value. The fewer checks made, the faster processing will go. It can be a tricky balancing act between when to do what and how many times to do it.
Most examples covered here will not use time for exit conditions simply for means of demonstration, though they may be better suited to use it, especially for larger scale implementations.
Multi-dimension Loops
Data represented in multiple dimensions, such as grid data, or the pixels in an image, often necessitate nested loops. When converting these loops into asynchronous operations, a similar approach to the asynchronous single loops is used. With nested loops, however, extra attention is needed to facilitate the initialization of loop variables when resuming a the loop. Specifically, it becomes important that saved indices for nested loops be reset to 0 at the end of the loop for its next iteration.
// pseudo code
for (i=savedIndexI; i<n; j++){
for (j=savedIndexJ; j<m; j++){
process(array[i][j]);
}
savedIndexJ = 0;
}
The resetting of the initial index value will have to be done for each nested loop and only after the first loop has already run. This is necessary to make sure the saved index is not reset before its able to be used for the first iteration of that loop.
Example: Color Gradient
Processing pixels in a bitmap is a common case where a nested loop would be needed. This example, using a class named RenderGradient, draws a gradient into a bitmap pixel by pixel using the setPixel() method of the BitmapData class.
The behavior of the RenderGradient class very closely follows that of the CaesarCipher class in the previous example. The class structure is almost identical with only a few notable exceptions:
Two loops are used; the inner loop index variable (savedX) is reset at the end of the loop block
A separate counter variable, iterationCounter, is used to count loop iterations for segmentation
Validation of the referenced BitmapData is required each time the operation is resumed
No return variable is necessary - the operation edits existing content rather than generating its own
The validation step is one worth noting. This was not an issue with the Caesar cipher example because all utilized data was copied into class member variables of the CaesarCipher instance. For RenderGradient, a BitmapData object is stored as a reference (through the bmp variable); it is not copied. The original BitmapData is modified as calculations are being made. This is important because if at some point the referenced BitmapData instance is disposed of by another process (using dispose()), the calculations made by the RenderGradient class would fail. This not unlike concurrency problems encountered in multi-threaded environments. In fact, these asynchronous operations are very thread-like and with references to external objects can experience similar consequences of thread interference. With ActionScript, however, you are at least guaranteed that no two scripts are being executed at the same time. This allows a one-time validation step at the start of an operation to handle such inconsistencies or unexpected states in referenced data. RenderGradient does this in a try..catch when defining the xn and yn variables from the width and height of the referenced BitmapData in the loop handler. Disposed BitmapData objects will cause an error when getting the width and height values allowing RenderGradient to exit gracefully with an Event.CANCEL event should an error occur there.
RenderGradient's validation does not prevent multiple instances from acting upon the same bitmap. It would be quite possible to have multiple RenderGradient instances editing the pixels of the same bitmap. The last instance called would simply overwrite all of the changes made by the previous. Other situations may require different levels of validation.
The RenderGradient class (available in source files)
Usage:
var renderer:RenderGradient = new RenderGradient(bmp, 0xFF0000, 0x0000FF);
renderer.run();
View live RenderGradient example.
Since all changes occur during the operation and no result value is needed, no complete event handler has been included here, though it may be necessary if there are other dependencies waiting for its completion.
Handling for..in and for..each Loops
Each of the previous examples use indexed loops for pausing and resuming iteration across multiple frames. They do not consider non-indexed looping such as for..in and for..each loops. These loops lack the ability to restart at an arbitrary location within the iteration. This prevents them from working very well with segmentation used to divide loops into smaller parts.
To handle these loops, you would need to first convert them into an indexed list, then use that list with segmentation.
// pseudo code
var indexedList = [];
for each(value in object){
indexedList.push(value);
}
// proceed normally...
This does incur a little extra overhead at the beginning of the operation but assuming the for/for..each loop itself isn't the cause of the slow processing, it should be negligible.
Sequences
Sometimes you aren't dealing with loops at all, and simply have chunks of code that take a long time to execute, which when combined, may stall screen rendering. For these cases, each chunk can be separated into individual functions and run individually. This can be done through an array or a linked list.
Array Sequencing
With array sequencing, each function is stored within an array and then each is called one frame at a time. The function sequence becomes the loop, and the processing of the array element is calling the function contained within that element.
// pseudo code
function processA(){
// time consuming process
}
function processB(){
// time consuming process
}
function processC(){
// time consuming process
}
var sequence:Array = [processA, processB, processC];
// Event.ENTER_FRAME loop through sequence array ...
The sequence array can be dynamically generated at runtime allowing for a mix and match of sequential function calls. In the end, this ends up being nothing more than a problem solved by the Single-dimension Loops solution.
Linked List Sequencing
Sequencing can also be approached through a linked list rather than an array. As a linked list, each function call indicates, when it is called, the next function to be called in an operation. An Event.ENTER_FRAME loop can then continuously call any referenced function until that reference is null.
// pseudo code
function processA(){
// time consuming process
next = processB;
}
function processB(){
// time consuming process
next = processC;
}
function processC(){
// time consuming process
next = null;
}
var next = processA;
// Event.ENTER_FRAME call next while non-null ...
The advantage of linked list sequences is that called functions decide through their own logic, while being called, which function is next to be called in the sequence.
Generally sequences such as these do not have as much control over the time it takes to perform each iteration. The separation of calculations across functions is a manual process which can be hard to distribute evenly, or even develop. If divided in smaller calculations, the Event.ENTER_FRAME event handler can attempt to call multiple functions within a sequence making its own determination as to when it should wait for the next frame to continue.
Example: Custom Bitmap Filters
This example revisits bitmaps with do-it-yourself, custom filters that applied to bitmap data in pixel by pixel looping. Each filter is a single function with loops go through a bitmap's pixels synchronously. Each filter being applied is added to a sequence which is run through asynchronously, applying one filter at a time, each frame. This is all being run by the ApplyFilters class, which accepts a BitmapData instance, and a sequence of filter functions with a BitmapData input that are to be applied to the BitmapData supplied.
The ApplyFilters class (available in source files)
Usage:
var sequence:Array = [twirl, wave, bubble]; // custom functions
var applicator:ApplyFilters = new ApplyFilters();
applicator.apply(bmp, sequence);
View live ApplyFilters example.
You may notice a slight change in design with the ApplyFilters class. Namely, the method that would have been called run() is now named apply(). The apply() method is also where data is passed to the ApplyFilters class. Previous examples were provided data in their constructors. Either place is acceptable; the choice is yours to make. The approach taken here moves further away from the idea that the class instances represent function calls and treats them as tools for performing a task; it favors reuse since a single instance can be used to perform multiple operations with different data.
This particular sequence implementation assumes each filter function will take around a frame to complete, or at least assumes that running more than one a frame would be too much, though more than one could be called each frame. In doing so, the standard loop approach with a saved index would be used.
In general, sequencing tends to be more versatile. This example has a dependency on a BitmapData instance that each function in the sequence needs to be provided. But you can imagine for situations where no arguments are used, a single sequencing class can be used in multiple situations. More adept sequencing classes can even listen for completion events of other sequences before continuing to the next function call.
Recursion
Recursion - when a function calls itself - represents yet another kind of looping. Recursive operations are neither indexed nor easily converted to something that is (such was the case with for..in and for..each loops). This requires a solution separate to those used before.
// pseudo code
function recursive(){
recursive();
}
recursive();
When functions call one another (including themselves), they are added to the call stack. The call stack contains all of the functions currently being called. When a function completes its execution or returns a value, it is removed from the call stack and the function which called it resumes executing. Recursive functions add themselves to the call stack repeatedly until some condition is reached that the last function returns a value rather than calling itself again.
Recursive function call's call stack
When creating an asynchronous version of a recursive operation, it may be necessary from within any function call on the stack - which are all the same function - that the operation would need to be exited in favor of resuming again on the next frame. When resumed, the stack would have to be rebuilt but without performing all of the same calculations that was previously made when the stack was constructed last time. To do this, conditions can be used to make sure blocks of logic are only run once.
// pseudo code
var processed = false;
function recursive(){
if (needToExit()){
throw error;
}
if (!processed){
// process
processed = true;
}
recursive();
}
In the above sample, the first thing you might notice is that the needToExit() condition throws an error rather than returning. Since this call could be within any number of nested function calls, throwing an error is the easiest way to escape the entire call stack. Code running the Event.ENTER_FRAME event handler would handle this with a try..catch and wait until the next frame to continue.
More importantly, this sample shows that the code within the processed block will only be run once, assuming it is ever completely run at all. If any of the nested function calls throws an error, the original function would be re-called in the next frame. As the stack rebuilds, all calculations that have already been processed will be skipped until a block that hasn't yet been run is reached and the processing continues.
Rebuilding call stack of a recursive function
Because data needs to be retained, not only in retaining the results of the calculations that were made within the process logic block but also with the necessity of the processed flag, each function call (as has been the case thus far) will need to be encapsulated in an object. The operation is complete when the first, root object can be called and completed successfully without it or any of its nested calls having thrown an error.
Two classes are needed to make this possible, a "runner" to make the call to the root recursion object, and one to define each of the recursion objects. The runner is the public facing class handling the Event.ENTER_FRAME event and dispatching an Event.COMPLETE event when the operation is complete. The recursion class handles the processing and recursive calling. The runner would work something like:
// pseudo code
var rootRecursive = new Recursive();
function enterFrame() {
try {
rootRecursive.call();
complete();
}catch(error){}
}
Any Recursive instance can throw an error to interrupt the operation causing a delay until the next frame. If no error is caught, the recursion calculations have completed. Note that it may be important to pay careful attention to what errors are thrown or could be thrown in case they are not related to making the operation asynchronous and instead indicate some other error.
Example: Factorial
The classic recursion example is the factorial function:
function factorial(n:int):int {
if(n == 0)
return 1;
return n * factorial(n - 1);
}
This is a simple recursive function which returns the factorial of the supplied value n. To make this function an asynchronous operation, two not so simple classes are needed - one to run (Event.ENTER_FRAME looping), and the other to handle the recursive logic. The Factorial class is represents the runner, and it's helper class FactorialOp contains the recursive logic.
The Factorial class (available in source files)
Usage:
var factor:Factorial = new Factorial(8);
factor.addEventListener(Event.COMPLETE, complete);
factor.run();
function complete(event:Event):void {
trace("Result: "+factor.result);
// Result: 40320
}
View live Factorial example.
Much of the Factorial class is still cookie-cutter in terms of what's been covered before. Theres a run() method, an Event.ENTER_FRAME loop and we even see the return of the result property to contain the return value of the operation. It's the loop handler function is where we see the changes.
Instead of running a loop and retaining an index, an instance of FunctionOp is being "called" every frame (via its call() method). When it completes without throwing an error, the operation is complete and the Event.COMPLETE event is dispatched. Each time that FunctionOp is called, it goes further and further into its recursion, rebuilding the call stack skipping any calculations that have been completed in a previous call.
There's actually no specific processed variable to indicate what has been run for this example. Instead the existence of the instance of the next recursive FunctionOp object, recursiveOp, is used. If that exists, it indicates that the code block in which it was created has been run and can be skipped in the next iteration.
The it is defined now, each FunctionOp will throw an error the first time its called using an interrupted flag. This actually creates a worst case scenario for overall processesing time since at no point in time will a frame execute more than one logic block. This approach is simply being used for demonstration purposes. For practical use, it may be necessary to pass some additional information into the call() function to know the state of the time taken or number of calculations made for a more accurately timed exit condition.
Example: Quicksort
The Quicksort sorting algorithm is another example of a recursive operation. Unlike with a factorial, quicksort uses two recusive calls. When getting into multiple recursive calls, more conditional blocking of logic is needed and it becomes more conveneint to use a single variable for identifying the execution of those blocks. The Quicksort and QuicksortOp classes do exactly that.
Instead of using a reference to the next QuicksortOp to know if logic has been executed, this Quicksort example uses a processed variable. It's used as a counter to indicate which of the multiple blocks of logic in each QuicksortOp call has been executed.
The Quicksort class (available in source files)
Usage:
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 sorter:Quicksort = new Quicksort(array);
sorter.addEventListener(Event.COMPLETE, complete);
sorter.run();
function complete(event:Event):void {
trace("Result: "+array);
// Result: 0,0,1,1,2,2,3,3,3,4,5,5,5,5,6,7,7,7,8,8,8,9,9,9
}
View live Quicksort example.
Synchronous with Asynchronous
Sometimes you need to create an asynchronous operation that isn't limited by processing speed, but other asynchronous operations. If one operation depends on the result of another asynchronous operation, that operation then has to be asynchronous. Any kind of network operation, for example, such as loading data from a remote server, would necessitate this.
When dealing with other asynchronous actions you're dealing with events. Events indicate an asynchronous operation completes (or fails) - something examples so far have been using. When one asynchronous operation depends on others, it needs to manage the events of the dependent operations and continue with its own actions after they complete - all of which is really just standard practice in your run of the mill ActionScript development.
Where it starts to get tricky is when you have a series of operations that may or may not be synchronous. Since such operations could be possibly asynchronous, an event would be necessary to identify completion regardless of whether the operation was truely asynchronous or not. When multiple operations are being run in a series, in the completion event handler of one opration, another would start. When that operation completes, the same handler would be used to repeat the process working through each operation in that series. If all of these operations end up being synchronous, you end up creating a recursion loop within this handler. With a enough operations in a series, this could create a stack overflow, with two many functions on the call stack. Truely asynchronous operations wouldn't suffer from this because their completion event that would have originated from within a call stack separate from the one being used by the series loop.
The solution to this problem, simply, is to avoid the recursion. This can mean waiting a frame (or using some other event) to restart the next set of operations. Though with enough operations in a series, this could be time consuming compared to a single-frame solution for a long set of synchronous variations.
An alternate approach would be to use looping in place of the recursion. A simple flag in a completion event handler can be used to identify an asynchronous result which can be used in a main series loop to know whether or not the loop can continue synchrnously. If asynchronous, the same flag can be used to let the event handler know that it needs to restart the series in its new call stack.
// pseudo code
var savedIndex = 0;
var resume = false;
function loop(){
var i, n = array.length;
for (i=savedIndex; i<n; i++){
resume = false;
process(array[i]);
if (!resume){
resume = true;
savedIndex = i + 1;
return;
}
}
}
function processComplete(){
if (resume){
loop();
}else{
resume = true;
}
}
Here, the resume variable indicates to the complete handler function whether or not it needs to restart the loop() function or to do nothing allowing the existing loop() loop already in the stack to continue synchronously. The loop, in finding that the value of resume hasn't changed can know that the complete event hasn't occured indicating that it needs to exit and wait for that to happen.
The handling of resume could have also been managed by a return value from process() (which translates to run()) indicating whether or not the operation was synchronous. But this would mean that the operation (the run()) was designed with that behavior in mind. All previous examples covered so far, for example, are not doing this but could potentially be synchronous if they never delay calculations to another frame.
Example: Reading Inline and Remote Text
Consider an XML file that references a collection of texts that need to be combined into a single document. The XML file may have text definined inline within its own XML, or link to an external text file which would need to be loaded into the player to be accessed. Each inline text can be read synchronously while external references need to be read asynchronously (loaded then read).
One class, GetElementText, is used to retrieve the text of any single XML element that may specify inline text or reference external text. It contains all of the logic to determine which is being used and how to get it. When the text is read, it dispatches an Event.COMPLETE event.
Another class, CreateDocument, is responsible for generating the document. It runs through an XML file creating GetElementText objects to get the text for each text element and adds the results to a single string representing the completed document. When this is complete, it too will dispatch an Event.COMPLETE event. In order to manage both circumstances of the GetElementText complete event (synchronous and asynchronous), and to help avoid a possible stack overflow from recursion, a resume variable is used to keep track of the GetElementText results as the XML is iterated over in a loop.
The GetElementText class (available in source files)
The CreateDocument class (available in source files)
Usage:
var document:CreateDocument = new CreateDocument();
document.addEventListener(Event.COMPLETE, documentComplete);
document.create(xml);
function documentComplete(event:Event):void {
trace("Result:\n" + document.text);
}
View live CreateDocument example.
Of course, even if every XML node were inline, and precautions weren't taken to prevent a stack overflow, one wouldn't have occurred in this example. However, with larger applications, and when working with a lot more data, that may not always be the case. It's better to be able to handle these kinds of situations in order to make sure your applications are scalable.
Conclusion
It may be uncommon that you need to create an asynchronous version of an operation, but it can be an important skill in helping you maintain fluid animations and uninterruped interactivity in your application. And that can go a long way in how users perceive your application.
You may find yourself in a situation where the topics covered here won't help. But there's almost always an alternative, whether its handing off expensive operations to a server, or using paging to limit the amount of data that you're working with at any given time. It's just a matter of finding the right solution for the job.
http://www.senocular.com/flash/tutorials/asyncoperations/?page=2
Date September 19, 2009
Language ActionScript 3.0
Target Flash Player 9+
Introduction
In Flash Player, both the execution of ActionScript and screen rendering is handled in a single thread. In order for the screen to be rendered, all code execution must be completed. For a Flash Player SWF running at 24 frames per second, this allows all ActionScript operations run within a single frame (frame scripts, events, etc.), at most, around 42 milliseconds to complete - this not accounting for the amount of time necessary to perform the actual screen rendering which itself may vary. If the execution of a block of code requires more time, Flash Player will appear to lock up until it is finished, or, after a default timeout period, just stop executing code.
Script timeout error dialog (Debugger Player only)
This tutorial will cover solutions to this problem - when you have operations that contain ActionScript calculations requiring more time than that which is allotted within any given frame. Such calculations are needed to be executed asynchronously; they would be non-blocking and complete at some point in time after the code block which invoked them has completed its own execution. This would give the renderer a chance to draw one or more times before the calculation is complete preventing the player from locking up.
Requirements
A moderate understanding of ActionScript 3.0
A compiler for ActionScript 3.0 (i.e. Flash Professional, Flash Builder, Flex SDK)
Source files (Flash CS4 format; ActionScript externalized)
Table of Contents
Page 1
Introduction
Requirements
Concepts
Single-dimension Loops
Example: Caesar Cipher
Time-based Exit Conditions
Multi-dimension Loops
Example: Color Gradient
Handling for..in and for..each Loops
Page 2
Sequences
Array Sequencing
Linked List Sequencing
Example: Custom Bitmap Filters
Recursion
Example: Factorial
Example: Quicksort
Synchronous with Asynchronous
Example: Reading Inline and Remote Text
Conclusion
Concepts
A running Flash Player instance processes a constant loop of consecutive frames. Even if there is not necessarily a timeline to animate through, frames are still rendered in the sense that a single frame representing the SWFs contents is repeatedly processed. Each frame consists of what can broken down into 2 parts: the execution of ActionScript through the AVM or ActionScript Virtual Machine, and a visual rendering of the screen by the Flash Player renderer. Code execution consists of both running ActionScript as well as idle time leading up to the next render. ActionScript is arbitrarily executed based on actions and events that occur between screen renders so idle time and occurrences may vary.
Frames both execute ActionScript and render the screen
When idle time is eaten away by the execution of cpu-intensive ActionScript, the time between each frame render will take longer, and the playback frame rate will suffer. What was 1 frame executed in 42 milliseconds may instead take 62 milliseconds, or even more if the ActionScript requires it.
ActionScript taking a long time to complete delays rendering
If ActionScript could be processed in a different thread of execution, this could prevent the renderer from being blocked. This is, however, not possible, as Flash Player, by nature, uses only one thread for both code execution and the rendering of the screen. So to prevent processor-intensive ActionScript from blocking the renderer, its calculations must be broken up into smaller, individual segments, or chunks, that can be run individually across multiple frames. Between each segment, the renderer can redraw allowing playback to go uninterrupted.
ActionScript stretched across multiple frames
This division of a single block of executing code across multiple frames is often non-trivial. Such code needs not only to know when to stop, but how to get back to where it was when picking up from where it left off. This adds the dependency of persistent data. What was before a function block with local variables must now become an object with property values.
Single-dimension Loops
The simplest, and probably the most common, circumstance requiring code to be segmented into chunks is with looping. A long loop can easily take up a lot of processing time, especially if heavy calculations are being made within each iteration of that loop.
Standard for loops use an index variable to keep track of a location within a list of items to be handled during iteration. A standard array loop would look something like:
// pseudo code
var i, n = array.length;
for (i=0; i<n; i++){
process(array[i]);
}
Segmenting the loop so that it can be handled in multiple iterations would require breaking out of the loop in the middle of the iteration with the ability to return to it later starting with the value of the index i at the point in time when the break occurred. The retained value of i represents the persistent variable that needs to be saved so that it can be referred to in another frame.
// pseudo code
var savedIndex = 0;
var i, n = array.length;
for (i=savedIndex; i<n; i++){
if (needToExit()){
savedIndex = i;
break;
}
process(array[i]);
}
Since the point of segmenting is to allow frame rendering, the execution of each chunk is appropriately handled in an Event.ENTER_FRAME event. An event handler is set when the loop starts and would need to be removed when the loop is complete. The loop is complete when the loop finishes without being exited, which, in the conext of a function would be handled by return.
// pseudo code
var savedIndex = 0;
function enterFrame() {
var i, n = array.length;
for (i=savedIndex; i<n; i++){
if (needToExit()){
savedIndex = i;
return;
}
process(array[i]);
}
complete();
}
The determination for loop exiting is decided by needToExit()whose implementation is ultimately up to you. This can be based on a set number of iterations, or even based off of an elapsed period of time as determined by getTimer(). Ideally, the time allotted for execution would be just enough to fill all available idle time up until the next render. There is no way to know how much time that actually is, so approximations will no doubt be a part of this process.
Example: Caesar Cipher
This example puts to practical application the looping of the characters within a string divided over multiple frames. To keep things self-contained and organized, a class, CaesarCipher, will be used to retain the necessary persistent data for the loop, though this data could also be retained within the current working scope (such as in timeline variables in Flash authoring).
The CaesarCipher class applies a Caesar cipher to a string. A standard, synchronous function for this operation would appear as:
function caesarCipher(text:String, shift:int = 3):String {
var result:String = "";
var i:int;
var n:int = text.length;
for (i=0; i<n; i++){
var code:int = text.charCodeAt(i);
// shift capital letters
if (code >= 65 && code <= 90){
code = 65 + (code - 65 + shift) % 26;
}
// shift lowercase letters
if (code >= 97 && code <= 122){
code = 97 + (code - 97 + shift) % 26;
}
result += String.fromCharCode(code);
}
return result;
}
The CaesarCipher class uses this same logic but allows the operation to continue over multiple frames before it's considered complete. This process then becomes asynchronous. Certain things had to be considered when implementing this:
Only display objects receive Event.ENTER_FRAME events
Return values no longer apply to asynchronous operations
The goal of the CaesarCipher class is to processes data; instances of it are not meant to be displayed on the screen. As a result, it will not be based off of another DisplayObject class which keeps the Event.ENTER_FRAME event from being available. This dependency, however, can be easily satisfied with composition, including a simple DisplayObject class member such as a Shape instance within the CaesarCipher class definition. Even though that instance will never reach a display list, it will still dispatch the Event.ENTER_FRAME event that can be used by an instance of CaesarCipher.
Concerning return values, an approach used by existing implementations of asynchronous operations will need to be used. For example, consider the URLLoader class. Instances are used to load external content through an asynchronous operation started by the load() call. When complete, an Event.COMPLETE event is dispatched and the loaded data can be accessible through the data property of the URLLoader instance. The CaesarCipher class uses a similar implementation. To start the operation, a run() method is used. When complete, an Event.COMPLETE event will fire, and the would-be return value can be accessible through the result property.
The CaesarCipher class (available in source files)
Usage:
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.
}
View live CaesarCipher example.
Though this class retains its result data in a class member variable, it could also be passed passed to the event, for example, if using a TextEvent object for the complete event. This approach, however, would necessitate the creating of a new Event class to handle the results data, so it's usually easier to stick to a class variable.
Because this example is using a short string, and because a Caesar cipher is not especially difficult for ActionScript to pull off, an iteration counter was used to determine when the next frame should be allowed to render between processing steps. It may be more common that a time-based approach will be used to make sure you're getting the most out of what time you have to calculate results in your frame.
Time-based Exit Conditions
Exiting an asynchronous script based off of the time taken to process a calculation will likely be the best solution for handling the segmentation. For this, you would need to know how much time has passed since the current frame's calculations started, and how much time, per frame, the calculations are allowed to have.
//pseudo code
var allowedTime = 1000/fps - renderTime - otherScriptsTime;
var startTime = 0;
var savedIndex = 0;
function enterFrame() {
startTime = getTimer();
var i, n = array.length;
for (i=savedIndex; i<n; i++){
if (getTimer() - startTime > allowedTime){
savedIndex = i;
return;
}
process(array[i]);
}
complete();
}
The times for rendering and other scripts will be estimated, but the more accurate they are, the better use of the available frame time you'll get. Note that these values can change depending on processor speed of computer running the script which can make them variable even at runtime. The closer it is to filling up the time available in the frame the better. You may even want to overshoot a little bit to make processing go as fast as possible at the expense of the frame rate depending on what's being presented to the user when the operation is being run.
For operations that contain a lot of looping with smaller calculations, you may not want the exit condition to be checked in every loop iteration - this especially when using getTimer() since it will help reduce the overhead of calling the function and looking up that value. The fewer checks made, the faster processing will go. It can be a tricky balancing act between when to do what and how many times to do it.
Most examples covered here will not use time for exit conditions simply for means of demonstration, though they may be better suited to use it, especially for larger scale implementations.
Multi-dimension Loops
Data represented in multiple dimensions, such as grid data, or the pixels in an image, often necessitate nested loops. When converting these loops into asynchronous operations, a similar approach to the asynchronous single loops is used. With nested loops, however, extra attention is needed to facilitate the initialization of loop variables when resuming a the loop. Specifically, it becomes important that saved indices for nested loops be reset to 0 at the end of the loop for its next iteration.
// pseudo code
for (i=savedIndexI; i<n; j++){
for (j=savedIndexJ; j<m; j++){
process(array[i][j]);
}
savedIndexJ = 0;
}
The resetting of the initial index value will have to be done for each nested loop and only after the first loop has already run. This is necessary to make sure the saved index is not reset before its able to be used for the first iteration of that loop.
Example: Color Gradient
Processing pixels in a bitmap is a common case where a nested loop would be needed. This example, using a class named RenderGradient, draws a gradient into a bitmap pixel by pixel using the setPixel() method of the BitmapData class.
The behavior of the RenderGradient class very closely follows that of the CaesarCipher class in the previous example. The class structure is almost identical with only a few notable exceptions:
Two loops are used; the inner loop index variable (savedX) is reset at the end of the loop block
A separate counter variable, iterationCounter, is used to count loop iterations for segmentation
Validation of the referenced BitmapData is required each time the operation is resumed
No return variable is necessary - the operation edits existing content rather than generating its own
The validation step is one worth noting. This was not an issue with the Caesar cipher example because all utilized data was copied into class member variables of the CaesarCipher instance. For RenderGradient, a BitmapData object is stored as a reference (through the bmp variable); it is not copied. The original BitmapData is modified as calculations are being made. This is important because if at some point the referenced BitmapData instance is disposed of by another process (using dispose()), the calculations made by the RenderGradient class would fail. This not unlike concurrency problems encountered in multi-threaded environments. In fact, these asynchronous operations are very thread-like and with references to external objects can experience similar consequences of thread interference. With ActionScript, however, you are at least guaranteed that no two scripts are being executed at the same time. This allows a one-time validation step at the start of an operation to handle such inconsistencies or unexpected states in referenced data. RenderGradient does this in a try..catch when defining the xn and yn variables from the width and height of the referenced BitmapData in the loop handler. Disposed BitmapData objects will cause an error when getting the width and height values allowing RenderGradient to exit gracefully with an Event.CANCEL event should an error occur there.
RenderGradient's validation does not prevent multiple instances from acting upon the same bitmap. It would be quite possible to have multiple RenderGradient instances editing the pixels of the same bitmap. The last instance called would simply overwrite all of the changes made by the previous. Other situations may require different levels of validation.
The RenderGradient class (available in source files)
Usage:
var renderer:RenderGradient = new RenderGradient(bmp, 0xFF0000, 0x0000FF);
renderer.run();
View live RenderGradient example.
Since all changes occur during the operation and no result value is needed, no complete event handler has been included here, though it may be necessary if there are other dependencies waiting for its completion.
Handling for..in and for..each Loops
Each of the previous examples use indexed loops for pausing and resuming iteration across multiple frames. They do not consider non-indexed looping such as for..in and for..each loops. These loops lack the ability to restart at an arbitrary location within the iteration. This prevents them from working very well with segmentation used to divide loops into smaller parts.
To handle these loops, you would need to first convert them into an indexed list, then use that list with segmentation.
// pseudo code
var indexedList = [];
for each(value in object){
indexedList.push(value);
}
// proceed normally...
This does incur a little extra overhead at the beginning of the operation but assuming the for/for..each loop itself isn't the cause of the slow processing, it should be negligible.
Sequences
Sometimes you aren't dealing with loops at all, and simply have chunks of code that take a long time to execute, which when combined, may stall screen rendering. For these cases, each chunk can be separated into individual functions and run individually. This can be done through an array or a linked list.
Array Sequencing
With array sequencing, each function is stored within an array and then each is called one frame at a time. The function sequence becomes the loop, and the processing of the array element is calling the function contained within that element.
// pseudo code
function processA(){
// time consuming process
}
function processB(){
// time consuming process
}
function processC(){
// time consuming process
}
var sequence:Array = [processA, processB, processC];
// Event.ENTER_FRAME loop through sequence array ...
The sequence array can be dynamically generated at runtime allowing for a mix and match of sequential function calls. In the end, this ends up being nothing more than a problem solved by the Single-dimension Loops solution.
Linked List Sequencing
Sequencing can also be approached through a linked list rather than an array. As a linked list, each function call indicates, when it is called, the next function to be called in an operation. An Event.ENTER_FRAME loop can then continuously call any referenced function until that reference is null.
// pseudo code
function processA(){
// time consuming process
next = processB;
}
function processB(){
// time consuming process
next = processC;
}
function processC(){
// time consuming process
next = null;
}
var next = processA;
// Event.ENTER_FRAME call next while non-null ...
The advantage of linked list sequences is that called functions decide through their own logic, while being called, which function is next to be called in the sequence.
Generally sequences such as these do not have as much control over the time it takes to perform each iteration. The separation of calculations across functions is a manual process which can be hard to distribute evenly, or even develop. If divided in smaller calculations, the Event.ENTER_FRAME event handler can attempt to call multiple functions within a sequence making its own determination as to when it should wait for the next frame to continue.
Example: Custom Bitmap Filters
This example revisits bitmaps with do-it-yourself, custom filters that applied to bitmap data in pixel by pixel looping. Each filter is a single function with loops go through a bitmap's pixels synchronously. Each filter being applied is added to a sequence which is run through asynchronously, applying one filter at a time, each frame. This is all being run by the ApplyFilters class, which accepts a BitmapData instance, and a sequence of filter functions with a BitmapData input that are to be applied to the BitmapData supplied.
The ApplyFilters class (available in source files)
Usage:
var sequence:Array = [twirl, wave, bubble]; // custom functions
var applicator:ApplyFilters = new ApplyFilters();
applicator.apply(bmp, sequence);
View live ApplyFilters example.
You may notice a slight change in design with the ApplyFilters class. Namely, the method that would have been called run() is now named apply(). The apply() method is also where data is passed to the ApplyFilters class. Previous examples were provided data in their constructors. Either place is acceptable; the choice is yours to make. The approach taken here moves further away from the idea that the class instances represent function calls and treats them as tools for performing a task; it favors reuse since a single instance can be used to perform multiple operations with different data.
This particular sequence implementation assumes each filter function will take around a frame to complete, or at least assumes that running more than one a frame would be too much, though more than one could be called each frame. In doing so, the standard loop approach with a saved index would be used.
In general, sequencing tends to be more versatile. This example has a dependency on a BitmapData instance that each function in the sequence needs to be provided. But you can imagine for situations where no arguments are used, a single sequencing class can be used in multiple situations. More adept sequencing classes can even listen for completion events of other sequences before continuing to the next function call.
Recursion
Recursion - when a function calls itself - represents yet another kind of looping. Recursive operations are neither indexed nor easily converted to something that is (such was the case with for..in and for..each loops). This requires a solution separate to those used before.
// pseudo code
function recursive(){
recursive();
}
recursive();
When functions call one another (including themselves), they are added to the call stack. The call stack contains all of the functions currently being called. When a function completes its execution or returns a value, it is removed from the call stack and the function which called it resumes executing. Recursive functions add themselves to the call stack repeatedly until some condition is reached that the last function returns a value rather than calling itself again.
Recursive function call's call stack
When creating an asynchronous version of a recursive operation, it may be necessary from within any function call on the stack - which are all the same function - that the operation would need to be exited in favor of resuming again on the next frame. When resumed, the stack would have to be rebuilt but without performing all of the same calculations that was previously made when the stack was constructed last time. To do this, conditions can be used to make sure blocks of logic are only run once.
// pseudo code
var processed = false;
function recursive(){
if (needToExit()){
throw error;
}
if (!processed){
// process
processed = true;
}
recursive();
}
In the above sample, the first thing you might notice is that the needToExit() condition throws an error rather than returning. Since this call could be within any number of nested function calls, throwing an error is the easiest way to escape the entire call stack. Code running the Event.ENTER_FRAME event handler would handle this with a try..catch and wait until the next frame to continue.
More importantly, this sample shows that the code within the processed block will only be run once, assuming it is ever completely run at all. If any of the nested function calls throws an error, the original function would be re-called in the next frame. As the stack rebuilds, all calculations that have already been processed will be skipped until a block that hasn't yet been run is reached and the processing continues.
Rebuilding call stack of a recursive function
Because data needs to be retained, not only in retaining the results of the calculations that were made within the process logic block but also with the necessity of the processed flag, each function call (as has been the case thus far) will need to be encapsulated in an object. The operation is complete when the first, root object can be called and completed successfully without it or any of its nested calls having thrown an error.
Two classes are needed to make this possible, a "runner" to make the call to the root recursion object, and one to define each of the recursion objects. The runner is the public facing class handling the Event.ENTER_FRAME event and dispatching an Event.COMPLETE event when the operation is complete. The recursion class handles the processing and recursive calling. The runner would work something like:
// pseudo code
var rootRecursive = new Recursive();
function enterFrame() {
try {
rootRecursive.call();
complete();
}catch(error){}
}
Any Recursive instance can throw an error to interrupt the operation causing a delay until the next frame. If no error is caught, the recursion calculations have completed. Note that it may be important to pay careful attention to what errors are thrown or could be thrown in case they are not related to making the operation asynchronous and instead indicate some other error.
Example: Factorial
The classic recursion example is the factorial function:
function factorial(n:int):int {
if(n == 0)
return 1;
return n * factorial(n - 1);
}
This is a simple recursive function which returns the factorial of the supplied value n. To make this function an asynchronous operation, two not so simple classes are needed - one to run (Event.ENTER_FRAME looping), and the other to handle the recursive logic. The Factorial class is represents the runner, and it's helper class FactorialOp contains the recursive logic.
The Factorial class (available in source files)
Usage:
var factor:Factorial = new Factorial(8);
factor.addEventListener(Event.COMPLETE, complete);
factor.run();
function complete(event:Event):void {
trace("Result: "+factor.result);
// Result: 40320
}
View live Factorial example.
Much of the Factorial class is still cookie-cutter in terms of what's been covered before. Theres a run() method, an Event.ENTER_FRAME loop and we even see the return of the result property to contain the return value of the operation. It's the loop handler function is where we see the changes.
Instead of running a loop and retaining an index, an instance of FunctionOp is being "called" every frame (via its call() method). When it completes without throwing an error, the operation is complete and the Event.COMPLETE event is dispatched. Each time that FunctionOp is called, it goes further and further into its recursion, rebuilding the call stack skipping any calculations that have been completed in a previous call.
There's actually no specific processed variable to indicate what has been run for this example. Instead the existence of the instance of the next recursive FunctionOp object, recursiveOp, is used. If that exists, it indicates that the code block in which it was created has been run and can be skipped in the next iteration.
The it is defined now, each FunctionOp will throw an error the first time its called using an interrupted flag. This actually creates a worst case scenario for overall processesing time since at no point in time will a frame execute more than one logic block. This approach is simply being used for demonstration purposes. For practical use, it may be necessary to pass some additional information into the call() function to know the state of the time taken or number of calculations made for a more accurately timed exit condition.
Example: Quicksort
The Quicksort sorting algorithm is another example of a recursive operation. Unlike with a factorial, quicksort uses two recusive calls. When getting into multiple recursive calls, more conditional blocking of logic is needed and it becomes more conveneint to use a single variable for identifying the execution of those blocks. The Quicksort and QuicksortOp classes do exactly that.
Instead of using a reference to the next QuicksortOp to know if logic has been executed, this Quicksort example uses a processed variable. It's used as a counter to indicate which of the multiple blocks of logic in each QuicksortOp call has been executed.
The Quicksort class (available in source files)
Usage:
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 sorter:Quicksort = new Quicksort(array);
sorter.addEventListener(Event.COMPLETE, complete);
sorter.run();
function complete(event:Event):void {
trace("Result: "+array);
// Result: 0,0,1,1,2,2,3,3,3,4,5,5,5,5,6,7,7,7,8,8,8,9,9,9
}
View live Quicksort example.
Synchronous with Asynchronous
Sometimes you need to create an asynchronous operation that isn't limited by processing speed, but other asynchronous operations. If one operation depends on the result of another asynchronous operation, that operation then has to be asynchronous. Any kind of network operation, for example, such as loading data from a remote server, would necessitate this.
When dealing with other asynchronous actions you're dealing with events. Events indicate an asynchronous operation completes (or fails) - something examples so far have been using. When one asynchronous operation depends on others, it needs to manage the events of the dependent operations and continue with its own actions after they complete - all of which is really just standard practice in your run of the mill ActionScript development.
Where it starts to get tricky is when you have a series of operations that may or may not be synchronous. Since such operations could be possibly asynchronous, an event would be necessary to identify completion regardless of whether the operation was truely asynchronous or not. When multiple operations are being run in a series, in the completion event handler of one opration, another would start. When that operation completes, the same handler would be used to repeat the process working through each operation in that series. If all of these operations end up being synchronous, you end up creating a recursion loop within this handler. With a enough operations in a series, this could create a stack overflow, with two many functions on the call stack. Truely asynchronous operations wouldn't suffer from this because their completion event that would have originated from within a call stack separate from the one being used by the series loop.
The solution to this problem, simply, is to avoid the recursion. This can mean waiting a frame (or using some other event) to restart the next set of operations. Though with enough operations in a series, this could be time consuming compared to a single-frame solution for a long set of synchronous variations.
An alternate approach would be to use looping in place of the recursion. A simple flag in a completion event handler can be used to identify an asynchronous result which can be used in a main series loop to know whether or not the loop can continue synchrnously. If asynchronous, the same flag can be used to let the event handler know that it needs to restart the series in its new call stack.
// pseudo code
var savedIndex = 0;
var resume = false;
function loop(){
var i, n = array.length;
for (i=savedIndex; i<n; i++){
resume = false;
process(array[i]);
if (!resume){
resume = true;
savedIndex = i + 1;
return;
}
}
}
function processComplete(){
if (resume){
loop();
}else{
resume = true;
}
}
Here, the resume variable indicates to the complete handler function whether or not it needs to restart the loop() function or to do nothing allowing the existing loop() loop already in the stack to continue synchronously. The loop, in finding that the value of resume hasn't changed can know that the complete event hasn't occured indicating that it needs to exit and wait for that to happen.
The handling of resume could have also been managed by a return value from process() (which translates to run()) indicating whether or not the operation was synchronous. But this would mean that the operation (the run()) was designed with that behavior in mind. All previous examples covered so far, for example, are not doing this but could potentially be synchronous if they never delay calculations to another frame.
Example: Reading Inline and Remote Text
Consider an XML file that references a collection of texts that need to be combined into a single document. The XML file may have text definined inline within its own XML, or link to an external text file which would need to be loaded into the player to be accessed. Each inline text can be read synchronously while external references need to be read asynchronously (loaded then read).
One class, GetElementText, is used to retrieve the text of any single XML element that may specify inline text or reference external text. It contains all of the logic to determine which is being used and how to get it. When the text is read, it dispatches an Event.COMPLETE event.
Another class, CreateDocument, is responsible for generating the document. It runs through an XML file creating GetElementText objects to get the text for each text element and adds the results to a single string representing the completed document. When this is complete, it too will dispatch an Event.COMPLETE event. In order to manage both circumstances of the GetElementText complete event (synchronous and asynchronous), and to help avoid a possible stack overflow from recursion, a resume variable is used to keep track of the GetElementText results as the XML is iterated over in a loop.
The GetElementText class (available in source files)
The CreateDocument class (available in source files)
Usage:
var document:CreateDocument = new CreateDocument();
document.addEventListener(Event.COMPLETE, documentComplete);
document.create(xml);
function documentComplete(event:Event):void {
trace("Result:\n" + document.text);
}
View live CreateDocument example.
Of course, even if every XML node were inline, and precautions weren't taken to prevent a stack overflow, one wouldn't have occurred in this example. However, with larger applications, and when working with a lot more data, that may not always be the case. It's better to be able to handle these kinds of situations in order to make sure your applications are scalable.
Conclusion
It may be uncommon that you need to create an asynchronous version of an operation, but it can be an important skill in helping you maintain fluid animations and uninterruped interactivity in your application. And that can go a long way in how users perceive your application.
You may find yourself in a situation where the topics covered here won't help. But there's almost always an alternative, whether its handing off expensive operations to a server, or using paging to limit the amount of data that you're working with at any given time. It's just a matter of finding the right solution for the job.
发表评论
-
柔软的带弹性的物理体验js源码和as源码
2011-09-08 01:41 0柔软的带弹性的物理体验js源码和as源码 -
[转]理解Flash Player 10.1和AIR 2中的安全更改
2011-08-10 15:21 1300http://www.infoq.com/cn/vendorc ... -
愤怒的小鸟 BOX2D FLASH
2011-08-09 01:27 0姊妹篇:Flash版《植物大战僵尸》源码今年就要大四啦,放暑假 ... -
[转]PT与PX区别
2011-08-01 00:43 1372http://blog.sina.com.cn/s/blog_ ... -
[转]一个Collision类,其中的block方法可以实现两个物体之间的碰撞检测。
2011-07-30 02:35 1389第二个是书中的源代码给出了一个Collision类,其中 ... -
Color depth change
2011-07-28 00:10 1518http://en.nicoptere.net/?p=8 ... -
AIR2.6以后flash player对图片的解码并非异步的
2011-07-14 19:54 0http://www.5uflash.com/flashjia ... -
[转] <What can you do with bytes ?> 第二章 第一节: 对象的复制
2011-03-16 00:07 1019http://bbs.9ria.com/viewthread. ... -
ActionScript Collections and Functional Programming
2011-03-13 20:18 895http://www.artima.com/weblogs/v ... -
Object Pooling in AS3
2011-01-12 11:56 1122If you're looking to save me ... -
[转]Callback Strategies
2010-09-28 14:39 1063http://jacksondunstan.com/artic ...
相关推荐
异步查询执行是数据库系统中的一个重要特性,它允许应用程序在发起查询后立即继续执行其他任务,而无需等待查询结果返回。这种方式提高了系统的响应时间和整体性能,尤其在处理大量数据或长时间运行的复杂查询时,...
This paper presents the rst comprehensive formalization of the Node.js asynchronous execution model and de nes a high-level notion of async-contexts to formalize fundamental relationships between ...
The .NET Framework provides three patterns for performing asynchronous operations: Asynchronous Programming Model (APM) pattern Event-based Asynchronous Pattern (EAP) Task-based Asynchronous Pattern ...
Asynchronous design has been an active area of research since at least the mid 1950’s, but has yet to achieve widespread use. We examine the benefits and problems inherent in asynchronous ...
Pro Asynchronous Programming with .NET teaches the essential skill of asynchronous programming in .NET. It answers critical questions in .NET application development, such as: how do I keep my program...
learning that uses asynchronous gradient descent for optimization of deep neural network controllers. We present asynchronous variants of four standard reinforcement learning algorithms and show that ...
5. 编程执行(Program Execution): - 描述了如何通过EMIF执行程序,例如从外部存储器加载指令和数据。 6. 电源管理(Power Management): - 提供EMIF在设备低功耗模式下的操作细节。 7. 模拟和调试考虑...
"Asynchronous Android Programming" English | ISBN: 1785883240 | 2016 | 394 pages About This Book Construct scalable and performant applications to take advantage of multi-thread asynchronous ...
[EBOOK]Asynchronous Circuit Design--Chris.J.Myers(包含源代码) With asynchronous circuit design becoming a powerful tool in the development of new digital systems, circuit designers are expected to ...
AJAX (Asynchronous JavaScript And XML)
The Task-based Asynchronous Pattern (TAP) is a new pattern for asynchrony in the .NET Framework. It is based on the Task and Task<TResult> types in the System.Threading.Tasks namespace, which are used...
Asynchronous Pluggable Protocol(简称APR)是Microsoft为Windows平台设计的一种机制,允许开发者创建自定义的网络协议,这些协议可以被应用程序异步地使用,从而提高系统性能并优化用户体验。 在给定的文件列表中...
标题"Asynchronous_Machine_wind_Turbine_IRFO_Control_matlab_control_"和描述"machine asynchrones control"揭示了这个压缩包中的内容与异步电机(尤其是应用于风力发电机)的控制策略有关,且采用了MATLAB作为...
异步消息通信技术是在不同进程、不同LabVIEW目标(网络上的系统)以及LabVIEW应用内发送消息的通用LabVIEW API。这种技术架构的关键点在于异步消息通信(AMC)库,它通过提供一组函数和工具,让开发者可以在LabVIEW...
异步FIFO(First-In-First-Out)架构是数字系统设计中的一个重要概念,尤其是在高速数据传输和通信接口中。FIFO是一种特殊的存储结构,其中数据按照先入先出的顺序进行读取和写入,类似于现实生活中的排队系统。...
在IT领域,异步套接字(Asynchronous Socket)服务器和客户端是网络编程中的核心概念,主要用于构建高效、可扩展的通信系统。本项目提供的代码示例深入探讨了这一技术,帮助开发者理解如何在实际应用中实现异步通信...
在Windows NT操作系统中,异步过程调用(Asynchronous Procedure Calls, APC)是一种重要的技术,用于在特定的线程上下文中执行任务,而无需线程持续监视或轮询。这种技术在Windows NT内核中扮演着重要角色,尤其是...
The introduction of Combine into the Swift ecosystem now gives you a native way to manage asynchronous events in Swift, meaning you don’t have to rely on third-party reactive frameworks for event-...
在深入讨论异步FIFO架构之前,首先需要了解同步FIFO的基本概念和原理。同步FIFO(First-In-First-Out)是一种数据结构,用于在计算机和数字逻辑系统中临时存储数据流。FIFO通常用于硬件设计,尤其是在需要数据在不同...