Keeping Your App Responsive – Part 2

You are reading part 2 of a three-part series:

Part 2: The Reason

Most (standard) buttons in your Titanium app change appearance when touched, to provide you with a visual confirmation. But though the look of the button changes instantly, the desired result of the button-tap sometimes takes a while. To understand why this is the case, lets start by looking at what happens at the native level.

As you might know every UI element you create in your JavaScript code is also created natively. In fact, every UI-object (e.g. Views, Windows, Buttons) you create in JavaScript is a near-empty shell (dubbed a proxy) that simply links to an actual UI-representation that resides in native-space.

Native

Figure 1 shows a pure native app, or at least some of its potential threads. If you are unfamiliar with threads, please first read Part 1 of this article.

Threading: Native
Figure 1: Native threads

A native app can have many different threads, all working in parallel. When used properly this will make for a smooth app. The amount of threads actually used is (largely) up to the developer. A simple app might only use two or three thread, but a more complex one might use many, many more. Contrary to what the figure might suggest, these threads aren’t always in existence (though the main/UI-thread is!). It is (usually) up to the developer to decide when to launch a new thread and when to stop it.

Titanium

Since Titanium is a native app, it has access to multiple threads as well. Figure 2 visualizes how your Titanium app uses these threads.

Threading: Titanium
Figure 2: Titanium’s use of native threads

In your application the UI is managed using a single native thread. In fact it’s managed by the main-thread. What this means is that this thread is the first to be created (by simply launching the app), and it will only stop when the app quits. Because it (primarily) handles everything UI-based, we will dub this thread the UI-thread for the remainder of this article.

Your logic

Every Titanium app has an additional thread that is even more important to you: the JavaScript-thread. This thread runs the JavaScript engine that in turn processes the code you wrote to create your app. In other words, this one thread is fully responsible for the processing of your application’s logic.

Threading: Application logic
Figure 3: The thread running your application’s logic

As was mentioned in Part 1, your Titanium app is multi-threaded but JavaScript itself is not. So even though your app uses multiple threads, the logic you defined does not. While a pure native app could use as many threads as it wants, you can only ever use one. This is visualized (for additional emphasis) in Figure 3. It shows the single thread that will run your Titanium app’s logic.

Interaction

When you touch a button, the UI-thread will immediately process this and update the visual state of the button. It will then send an event to the JavaScript-thread in order to let it know a button was tapped. This is why the state of the button changes right away, but the desired result might not yet occur. Both actions are handled by a different thread. The UI-thread is simply waiting for instructions, either from the user or from the JavaScript-thread. Since it isn’t doing any of the heavy lifting (the JavaScript thread does that) it is seldom busy. This means a button-tap will register right away in the UI-thread, but could end up in a traffic-jam in the JavaScript-thread.

Titanium: Thread Interaction
Figure 4: Thread Interaction

Figure 4 shows an example of the interaction between the two threads. In this example a button-tap will result in the opening of a new window.

The Call Stack (or Queue)

When a user taps a button, the UI-thread will communicate this to the JavaScript thread. You might expect its event-handler to be executed right away, but oftentimes this is not the case. Since JavaScript is run in a single thread -and is single-threaded by nature- all code has to be executed sequentially. This means that if some code is being executed when the UI-event arrives, the event will simply have to wait until the current process ends.

In order to facilitate this behavior JavaScript engines maintain a so-called Call Stack. Whenever anything needs to be executed, it is simply added to this stack. If at some point the JS engine finds itself with nothing to do, it will check this stack for more work. In Titanium this Call Stack is being processed FIFO (first-in, first-out), similar to most web browsers. This means that the process that was added to the stack the longest ago will be executed first.

So, when a user taps a button, the UI-thread will trigger an event in the JavaScript-thread, which will then add this event(-handler) to its internal call stack.

Timers

Contrary to what you might think, the setTimeout-, and setInterval-functions (and their ‘clear’-counterparts) are not part of the JavaScript language specification. They were however introduced alongside JavaScript itself in Netscape 2.0, also referred to as DOM 0. Just as with JavaScript itself, all future browser -and hence JavaScript engines- have adopted them.

So, though JavaScript itself has no support for any kind of asynchronous execution, these timers make the semi-asynchronous execution of code available to you. Note the emphasis on “semi”, because while the execution is delayed, it is still run synchronously (not in parallel!) later on. When a timer or interval expires the function that is to be executed is simply added to the Call Stack as well.

Delayed Response

If the JavaScript engine is busy, then neither the UI-event nor the expired timer will be handled immediately. These processes will simply have to wait until the previously queued processes and the one that is currently executing, have finished.
All of these events are simply stacked and then executed in the order in which they were added.

To make this whole process more clear, let’s look at an example that will show you why your app responds the way it does!

Example: A Slow Response

See the code snippet below:

function foo() {
    setInterval(bar, 500);
    setTimeout(nerf, 600);

    // ...
    // some heavy processing that will take 1.6 seconds to execute
}

function bar() {
    // ... 
    // some code that will take 300 milliseconds to execute
}

function nerf () {
    // ... 
    // some code that will take 200 milliseconds to execute
}

function onTap() {
    // ...
    // some code that will take 100 milliseconds to execute
    // open a dialog window
}

Please imagine the following scenario:

Somewhere out there a user launches your app. After a very brief initialization a controller you defined is executed that in turn immediately opens a window. This controller then invokes foo (see the code snippet above) to make sure the window gets filled with accurate information that it retrieves from the devices’ storage (e.g. file, properties, database).

Amongst other things, the window that was just opened contains a single big red button that should open a dialog when tapped. Prior to invoking foo, the controller made sure to assign the onTap method to this button’s click-event.

Up until this point no events or timers of any kind were triggered, so the Call Stack is completely empty.

Let’s have a look -step by step- at what happens internally when this snippet gets executed and the user at some point decides to touch the button.

Step 1: Foo is executed

When foo gets executed it starts by setting a timeout and an interval. It then loads data from the device’s storage and performs some heavy duty processing. But all you really need to know is that it simply will keep the JS engine occupied for about 1600 milliseconds (or 1.6 seconds).

Figure 5 shows the state of the JS engine after the first 499 milliseconds have elapsed.

Example: After 499 ms
Figure 5: Call Stack & Timeline after 499 ms

The big blue arrow contains the events that are currently planned. At this point it contains the timeout-, and the interval-events. The dark part of the blue arrow represents to what point execution has progressed.
Note that the Call Stack remains empty, since no timer or interval has expired yet at this point.

The graph below the arrow represents the execution-history. It shows what was executed and when, and also what is being executed currently.

Step 2: User touches button

The interval for bar was set to execute every 500 ms and this is the first event to occur. But since foo is still running, the interval’s handler is not executed right away. It is instead added to the Call Stack.
Likewise, after 600 ms the timeout on nerf expires and this handler is added to the stack as well.

Note that these handlers are not added to the stack when you perform the call to setInterval or setTimeout. Only when a timer expires is the actual handler added to the stack.

Since the window has now been open for a while, after 900 milliseconds (almost a full second) the user decides to tap the big red button. The UI-thread instantly passes this event to the JavaScript thread and immediately gets back to doing absolutely nothing. Like with the timers, since foo is still executing, this click-event is added to the stack as well.

Figure 6 shows the state of the JS engine after the first 999 milliseconds have elapsed.

Example: After 999 ms
Figure 6: Call Stack & Timeline after 999 ms

It’s immediately clear that the user will have to wait awhile before the big red button will be doing anything.

Step 3: The next interval

After a full second the interval’s timer expires once more. This time however bar is not added to the Call Stack, since it is already on the stack. When an interval’s timer expires, the JS engine first checks if its handler is already on the Call Stack. If this is the case the engine simply ignores this event and continues. If not, it is added.

After 1.5 seconds the interval triggers again, but with the exact same result. The stack remains unchanged.

Figure 7 depicts the situation after 1599 milliseconds went by.

Example: After 1599 ms
Figure 7: Call Stack & Timeline after 1599 ms

At this point bar still did not execute, though it should have three times by now. In fact, two invocations of bar have already been discarded!

Step 4: Foo wraps up

Finally, after 1.6 seconds foo wraps up and its execution ends. The engine now checks the Call Stack for the oldest item residing there. This item is then popped from the stack and executed. In this case bar was there first, so it is removed from the stack and executed. Note that this occurs more than a second after you had planned it to.

When bar is done executing, nerf immediately follows after 1900 ms, more than a second later than it was meant to.

Figure 8 shows the state of the JS engine after the first 1999 milliseconds have elapsed.

Example: After 1999 ms
Figure 8: Call Stack & Timeline after 1999 ms

The execution-log now contains bar that was executed and nerf that is currently executing.

Step 5: Feedback!

While nerf is executing, after 2 seconds have elapsed the interval triggers again. Since bar is no longer on the Call Stack, it is added again.

Then (drum roll!) after 2100 ms the button-tap finally comes through and onTap will start executing.

When the big red button is done executing and the user can finally lay eyes on that dialog, it is bar‘s turn again.

Figure 9 depicts the situation after 2499 milliseconds went by.

Example: After 2499 ms
Figure 9: Call Stack & Timeline after 2499 ms

After 2500 ms the interval on bar will trigger once more. But because the Call Stack is now empty bar will execute immediately for a change.

From this point onwards, as long as the user does not interfere, it should execute every 500ms. Let’s hope the user satisfies himself with just looking at the app from now on!

Conclusion

The first thing to take away here is that the user had to wait for over 1.3 seconds before the action he requested was finally executed. That clearly does not make for a very responsive app.

Another thing to notice is that even though timers expire exactly when you told them to, that does not mean their handlers execute right away as well. The only guarantee you have is that the handler will not be executed earlier.
The same goes for intervals, but they also show some additional unexpected behavior. After 2.49 seconds went by you would logically expect bar to have been invoked 4 times, but instead it was invoked only twice. It doesn’t catch up either, the other invocations were simply dropped.

Dialog-frenzy

The example above might be more complex than your app is. But even if you use no timeouts and intervals, the principle remains the same. If you execute a lengthy bit of code it will block user-interaction. Let’s shortly take a look at what would happen, had a user tapped the button twice.

Using the example above, let’s assume the user taps the big red button again after 1900 seconds have elapsed (so after having waited for over a second). The UI-thread would, oblivious to the internal state of the JS engine, simply send another click-event to the JS-thread. Contrary to intervals that are part of the JS engine, the incoming event will be treated as a new event and simply be added to the Call Stack, even though a similar event already resides there.

The result then is to be expected, the onTap-method will be executed twice. Two dialogs will be shown instead of one.

The solution

Now how do you go about to prevent this from happening? You don’t want your app to become unresponsive, but whatever it is foo is doing (in the example), it needs to be done!

Stay tuned for part 3, where you will learn how to circumvent this issue and keep your app responsive!

Ronald Treur is a freelance developer and the co-founder of Snowciety, a multi-platform mobile app created using Titanium & Alloy. He has been developing websites since the age of 11 (starting out on paper due to lack of a real computer) and has been working on mobile apps since Titanium 1.5.
For development he has experience with Javascript, PHP and Java and to a lesser extent C and C++. For webdesign he relies on his knowledge of HTML5 and CSS3. Other interests include game-design, movies and talking at meetups (recent addition!).


Comments