The Benefits of NativeScript's Single Threading Model -

May 10, 2016 0 Comments nativescript

The Benefits of NativeScript's Single Threading Model -

There's been a lot of discussion around the threading model in NativeScript recently. Questions pop-up like, "Why does NativeScript run JavaScript on the main UI thread while other solutions utilize a separate thread?" and, "Isn't this approach hurting performance?"

These are good questions.

In this post I will try to share perspective on threading from the NativeScript core team. We'll look at the benefits of running JavaScript on the UI thread, the complexity of solving the threading issue "The Right Way™" and our future plans.

Let me start with some of the most important principles that guide the NativeScript project.

Guiding Principles

In NativeScript, we leveraged Telerik's experience creating productivity tools for developers to create a framework for building cross-platform native mobile apps that any developer can quickly pick up and use. From the beginning, NativeScript has focused on delivering a solution that should:

  • Make all native mobile platform APIs available to application JavaScript (without the need for native plugins);
  • Deliver native performance;
  • Optimize the development experience for web developers (especially Angular/TypeScript developers).

These principles guide the architectural decisions that we make as NativeScript evolves. Let's take a closer look at how these principles have driven our decisions around threading.

Why does NativeScript run JavaScript on the UI thread?

The answer is simple. Running JavaScript on the UI thread allows NativeScript to deliver on one of its core guiding principles: provide high performance access to 100% of native platform APIs through JavaScript. Period. No trade-offs. No limits.

To put it another way, if an API exists in iOS or Android (or even a native library for iOS or Android), you can call it via JavaScript in NativeScript. There is nothing between you and the ability to do anything a native app created with ObjC/Swift/Java can do, but with JavaScript.

This is a powerful capability, and unique, even among alternatives like React Native and Fuse that require native code plugins and new layers of abstraction to interact with native APIs.

To deliver this capability, it is essential that interactions between the native UI and the JavaScript calling other native APIs is fast. In our research, we found that the most efficient way to deliver the fastest apps, while enabling 100% API access, is by putting application JavaScript on the same thread as UI code. This minimizes marshalling performance problems and has the advantage of creating a programming model that very closely emulates the underlying native architectures without adding new layers of abstraction.

Our synthetic runtime benchmarks demonstrate that. When compared with React Native, NativeScript, running on the main UI thread, is orders of magnitude faster calling native APIs, a very common behavior in JavaScript-driven native apps. (Note: the below tests were performed on iOS8+)

Disclaimer: These tests are synthetic and are mainly intended to prove the fast and efficient JavaScript-to-Native (and vice-versa) transitions NativeScript provides by running JS on the main UI thread.

There is a time and a place for background threads, but putting all app code on a different thread from the UI by default clearly affects the efficiency of calling native APIs.

To summarize: in NativeScript we run JavaScript on the UI thread so that the native UI and application JavaScript - which frequently call native platform APIs - can communicate as fast as possible with as little marshalling cost as possible. And our benchmarks show this is fast. Perhaps most importantly, the resulting architecture uniquely enables access to 100% of native APIs via JavaScript.

Won't running JavaScript on the UI thread cause frames to drop?

Yes and no.

First, for those not aware, "dropped frames" are the result of the UI thread being too busy to deliver smooth, 60fps refreshes. When an app begins to drop frames, the UI "stutters" and introduces what many developers now call " jank." It's an all too common problem in hybrid apps, and a big reason we created NativeScript in the first place.

An example of how fast, fluid and responsive apps built with NativeScript are is our marketplace demo:

A quick teaser for those who love CSS - the animations on this screen are entirely made via CSS

This application is available on the iOS App Store and Google Play, so you can download and experiment with it for yourself.

For most operations, especially in common "business apps," a device has no trouble handling the NativeScript JavaScript on the UI thread while delivering 60fps. However, you may see performance issues when you run a CPU-intensive operation on the UI thread.

Not coincidentally, this is the exact same problem you'd encounter creating "raw" native apps for iOS or Android using Obj-C, Swift or Java. By default, native code runs on the same thread as native UI, so CPU-intensive operations in native code can also degrade app performance and cause dropped frames.

NativeScript, true to its billing, is behaving identically to native apps.

It follows, then, just as in native apps, the correct solution is to selectively offload CPU-intensive code to background threads so that animations and other UI-related code continue to run smoothly. True in native apps. True in NativeScript.

Okay. Cool. How do you offload to background threads in NativeScript?

Straight to the point: NativeScript does not provide an official cross-platform mechanism for doing arbitrary JavaScript work on a background thread...yet.

NativeScript started by focusing on creating a fast and powerful single-threaded native API access layer that would deliver smooth, high-performance UI for the most common app scenarios. As discussed above, we've focused on minimizing marshalling and creating a runtime that behaves very similarly to native apps.

Now that this layer is stable and performant, the next step is to add the background thread APIs to enable selective offloading of the most CPU-intensive tasks.

Background threads in NativeScript today

"Wait? I thought you said background threads were NOT in NativeScript today."

Ah! Close. Background JavaScript threads are not yet in NativeScript, but there are many places where native background threads already exist in NativeScript apps.

As it turns-out, most use cases for background work are not often associated with application-specific code. They are generic use cases and usually involve http or network requests, database access, image decoding and processing, heavy IO operations and so on.

For a significant number of these scenarios, NativeScript modules and plugins already exist to do the generic work on a background thread, dispatching the output to the main UI thread when the heavy lifting is done. The out-of-the-box HTTP module, for example, uses a background thread to process network requests, as does the push-notification plugin available on NPM.

Of course, there are still uncovered scenarios when you may need to utilize a background thread for generic CPU-intensive work.

In those cases, the best practice in NativeScript today is to write the CPU-intensive code natively on a native background thread and then consume it in NativeScript app JavaScript as a plugin (similar to how the previously mentioned HTTP module is implemented).

That's today. It's an advanced approach, mostly intended for plugin developers.

For the "typical" NativeScript developer, we still want to provide an API that makes it possible to do arbitrary work on background threads in a cross-platform way without writing native code. Think: " Web Workers " for NativeScript. So let's look at what's next for background threads in NativeScript.

Enabling JavaScript background threads in NativeScript

If you end-up with a block of application code that is just too heavy to run on the main UI thread, and there isn't a generic "background thread enabled" plug-in available to assist, the next step in NativeScript is cross-platform, JavaScript-enabled background threads.

There are some important details we're working through to ensure an intuitive developer experience as we add background JavaScript to NativeScript:

  • Background JavaScript runs in its own context (just as Web Workers) and there is no direct access to code initialized on the main thread (and vice-versa);
  • Access to native APIs in background threads must be limited to non-UI APIs only; all UI access must happen on the main UI thread to avoid access violation errors;
  • Making too many calls to the UI thread from a background process could produce worse performance due to the marshalling taxes we discussed earlier.

Our goal is to gracefully handle these invalid conditions and provide meaningful error messages in NativeScript so that working with background JavaScript threads is as easy as possible. This is an important API, so any input and feedback is welcome to make sure we get it right.


So, what have we learned?

  1. Running app code on the UI thread by default allows NativeScript to expose all native APIs to app JavaScript in an efficient and high performing way.
  2. Many common scenarios that require background thread processing are already covered by NativeScript plugins and modules.
  3. Background threads (with JavaScript) are coming to NativeScript (soon)!

We are starting work on JavaScript background threads now that NativeScript 2.0 has been released.

In the meantime, running JavaScript on the UI thread is a model that makes the NativeScript architecture as close to the underlying native platforms as possible. We think it has many advantages and is an important difference between NativeScript and other solutions like React Native and Uno in Fuse, which require native code plugins for native API access and have programming models that diverge more heavily from the underlying native platforms.

We want your feedback!

Hopefully this post sheds some light on how and how the NativeScript core team thinks about threads in NativeScript, and our plans to extend background thread support this summer. We'd love to hear your feedback on the NativeScript architectural model and how you plan to use background threads in your apps. Together, let's keep making NativeScript a great and unique framework for building JavaScript-powered native apps.

Related Resources Header image courtesy of Marlon M

The Benefits of NativeScript's Single Threading Model -

Tag cloud