다음을 통해 공유


Executing code

The code in an app defines its behavior. Understanding how your app executes its code can help you improve its performance.

How your app executes code

Two of the most important things to know about how your app executes code is that app code is event driven and all components share the same thread.

  • Event driven

    The Windows Store app using JavaScript platform is event-driven. This means that your code executes only when it's triggered by an event (the only exception is that code may also be executed at parse time).

  • Single threaded

    The Windows Store app using JavaScript platform is single-threaded. This means that all components of the platform run on the same thread, including both the UI and JavaScript code execution, and only one piece of code can execute at a time. The only way to run your code in another thread is to use web workers. Web workers create a parallel thread used only for JavaScript code execution. In Windows 8, CPU cycles are taken away from background apps and given to the foreground app so that it can provide a more responsive UI.

The platform executes the code for each event in the queue one at a time, as shown in this illustration:

The illustration shows that the platform is currently processing a touch event handler, while a timer event, a postMessage event, and another timer event wait in the queue to be processed. When code for one event handler is executing, the platform can't respond to any new events. If a new event takes place while the touch event handler code is executing (suppose the user touches another item on the screen), the new touch event handler can't be executed until the current one completes. If the initial touch event handler code is long running, there is a delay between the user's interaction and your app's response, resulting in a sluggish response times.

There are ways to avoid these performance pitfalls. The next sections describe some of them.

Avoid running code that takes a long time to execute

When the user interacts with an app by touching the screen and you handle the interaction in an event, the app can't respond to the next user interaction until your code for the first event handler finishes. To ensure your app stays responsive, you can use these techniques to execute your code:

  • Use Web Workers to move execution to another thread

    If your code takes a long time to execute, move it to a Web Worker. By using Web Workers an app can define a set of code to execute in another thread, separate from the main thread. One example of a good use of Web Workers is to calculate the computer's move in game where the user is playing against it. Typically, the code that calculates the computer's next move takes a lot of time to execute. If this code is executed on the main thread, the app can't respond to additional interactions from the user until the code finishes executing. Instead, the app can move the logic to a Web Worker to execute the logic on a different thread, and then inform the main thread after the computation is complete.

    Tip  While an expensive (long-running) operation is taking place on the main thread of the app, no other events can be processed. So, offload expensive work to a Web Worker. Some operations, such as calculating the next move in a game, take a long time to execute and can delay responses to other events.

     

  • Use setImmediate to execute code on the UI thread

    Another tool that can help keep your app responsive is the setImmediate function. Use this function to define a callback method that executes when the platform has finished processing all events and made display updates.

    Although the setImmediate function provides a way to execute code on the UI thread when no other events are waiting to be processed, it doesn't prevent new events from taking place while executing the callback. So, don't use the setImmediate function to execute expensive code. Use it only to perform quick operations without interfering with events that might have already been queued.

    Although setImmediate can help an app more efficiently execute code on the UI thread, over-use of setImmediate can cause performance issues. Between queued setImmediate calls, execution could be yielded to the host, potentially allowing expensive operations such as a layout pass to take place. For this reason, we recommend that you batch related updates, such as UI updates, into a single setImmediate call.

  • Avoid creating timers than run for less than 10ms

    When you specify a time of less than 10ms for a timer function (such as setTimeout), the system must load additional binaries to achieve the timer. Loading the extra binaries can degrade performance, so, when possible, avoid creating timers that run for less than 10ms.

Use asynchronous APIs

To help keep your app responsive, the platform provides asynchronous versions of many of its APIs. An asynchronous API executes in the background and informs the app after it's completed by firing an event. When calling an API from the main thread, use the asynchronous version if it's available.

One common example of a useful asynchronous API is XMLHttpRequest, which is a networking API. It lets an app initiate a request to a remote server. The time it takes to complete the request is unknown. This example executes an open operation asynchronously by passing true to the XMLHttpRequest.open function to execute asynchronously.

var xhr = new XMLHttpRequest();
xhr.onload = function(){
// The request completed 
};
xhr.onerror = function(){
// The request failed with an error
};
xhr.open("GET", "mysite.com/page.aspx", true);
xhr.send(null);

When the code in the example executes, the main thread begins a background request to the specified URL and then continues executing other code. After the request completes, it raises the load event. Your app can handle the event to process the XMLHttpRequest results.

If the example passed a value of false to the XMLHttpRequest.open function instead, the JavaScript engine would block any other code from executing until the XMLHttpRequest completed.

Tip  When executing code on the main thread, use asynchronous APIs when they're available. When you use a synchronous API, all other code execution on the main thread is blocked until the API completes, so the app can't respond to new events while the API is executing.

 

Cancel ongoing requests where appropriate

Requests, such as networking requests, are easily initiated, yet often forgotten. It's common for an app to initiate a network request and not cancel it after it's no longer needed.

This often happens when apps implement the search contract and listen for the change event on the search box. Some apps must perform a network request to get results for the user, and initiate the request when the event fires. But often the app overlooks the previous search request: if there was a previous search request, it's no longer needed and should be cancelled.

Most expensive APIs, such as FileReader or XMLHttpRequest, provide a way to cancel the operation. By canceling, the app can regain those resources that would have been used to complete the operation.

Use the scheduler

The scheduler API, introduced in Windows 8.1, lets you set the priority of tasks (also known as work items or jobs) and manage them. This helps you write Windows Store apps that use system resources more efficiently and provide a more responsive experience for your users.

To use the scheduler API, call the schedule function, specifying a task and a priority. Here's an example.

function scheduleTasks() {
    var S = WinJS.Utilities.Scheduler;

    WinJS.Utilities.startLog("example");
    
    S.schedule(function () {
        WinJS.log("Running task at aboveNormal priority.", "example");
    }, S.Priority.aboveNormal);
    WinJS.log("Scheduled task at aboveNormal priority.", "example");
    
    S.schedule(function () {
        WinJS.log("Running task at idle priority.", "example");
    }, S.Priority.idle);
    WinJS.log("Scheduled task at idle priority.", "example");

    S.schedule(function () {
        WinJS.log("Running task at belowNormal priority.", "example");
    }, S.Priority.belowNormal);
    WinJS.log("Scheduled task at belowNormal priority.", "example");

    S.schedule(function () {
        WinJS.log("Running task at normal priority.", "example");
    }, S.Priority.normal);
    WinJS.log("Scheduled task at normal priority.", "example");

    S.schedule(function () {
        WinJS.log("Running task at high priority.", "example");
    }, S.Priority.high);
    WinJS.log("Scheduled task at high priority.", "example");

    S.schedule(function () {
        WinJS.log("Running task at min priority.", "example");
    }, S.Priority.min);
    WinJS.log("Scheduled task at min priority.", "example");

    S.schedule(function () {
        WinJS.log("Running task at max priority.", "example");
    }, S.Priority.max);
    WinJS.log("Scheduled task at max priority.", "example");    
}

/* Produces the following output:

example: Scheduled task at aboveNormal priority.
example: Scheduled task at idle priority.
example: Scheduled task at belowNormal priority.
example: Scheduled task at normal priority.
example: Scheduled task at high priority.
example: Scheduled task at min priority.
example: Scheduled task at max priority.
example: Running task at max priority.
example: Running task at high priority.
example: Running task at aboveNormal priority.
example: Running task at normal priority.
example: Running task at belowNormal priority.
example: Running task at idle priority.
example: Running task at min priority.
*/

The schedule function returns an object that implements the IJob interface, which you can use to pause, resume, and cancel the task that you scheduled.

For more code that shows how to use the schedule function, see the HTML Scheduler sample.

Manage memory consumption

The dispose model, introduced in Windows 8.1, is a pattern that allows elements and controls to optionally release resources at the end of their lifetime to prevent memory leaks. An element or control can implement it optionally. Windows Library for JavaScript 2.0 controls that have resources to release (for example, ItemContainer, SearchBox, and SettingsFlyout) implement this pattern. To use this pattern, call the control's dispose method (for example, ItemContainer.dispose, SearchBox.dispose, SettingsFlyout.dispose), when the control is no longer needed, such as when you're navigating away from a page or when the app is shutting down.

Here's an example of how to use the dispose model. A custom date picker control is created and shown. This control includes a ListView, a DatePicker, a button, and a div. A separate div containing 2 button controls is also declared. When the Create control button is clicked, a new instance of the custom date picker control is created and shown. When the Dispose button is clicked, all of the controls' resources are released, and all of the controls become unusable and unresponsive.

<!-- ... -->
<body>
    <div id="listView" data-win-control="WinJS.UI.ListView"></div>
    <div id="datePicker" data-win-control="WinJS.UI.DatePicker"></div>
    <div>
        <button id="createControl" onclick="onclickCreateControl()">1. Create control</button>
        <button id="disposeButton" onclick="onclickDispose()">2. Dispose</button>
    </div>
    <div id="dateDiv"></div>
</body>
<!-- ... -->
function onclickCreateControl() {
    // Create an example composite date picker control.
    WinJS.Namespace.define("WinJS.Samples", {
        CustomDatePicker: WinJS.Class.define(
            function (element) {
                this._element = element || document.createElement("div");
                this._element.winControl = this;
                this._disposed = false;
                this._submitButton;
                this._submitListener;
                this._datePicker;
                this._resultDiv;

                WinJS.Utilities.addClass(this._element, "win-disposable");
                this._createSubControls();
                this._wireupEvents();
            },
            {
                element: {
                    get: function () {
                        return this._element;
                    }
                },
                _createSubControls: function () {
                    var containerDiv = document.createElement("div");
                    WinJS.Utilities.addClass(containerDiv, "customDatePicker");
                    this._element.appendChild(containerDiv);

                    // Create a date picker.
                    var datePickerDiv = document.createElement("div");
                    this._datePicker = new WinJS.UI.DatePicker(datePickerDiv);
                    this._datePicker.current = new Date(2013, 0, 1);
                    containerDiv.appendChild(datePickerDiv);

                    // Create a button.
                    var submitContainer = document.createElement("div");
                    containerDiv.appendChild(submitContainer);
                    this._submitButton = document.createElement("button");
                    this._submitButton.textContent = "Submit";
                    submitContainer.appendChild(this._submitButton);

                    // Create a div for the result.
                    this._resultDiv = document.createElement("div");
                    containerDiv.appendChild(this._resultDiv);
                },
                _wireupEvents: function () {
                    this._submitListener = this._handleSubmitClick.bind(this);
                    this._submitButton.addEventListener("click", this._submitListener, false);
                },
                _handleSubmitClick: function () {
                    this._reportResult.bind(this)();
                },
                _reportResult: function () {
                    this._resultDiv.textContent = "You picked " + this._datePicker.current;
                },
                // Implement a dispose function.
                dispose: function () {
                    if (this._disposed) {
                        return;
                    }

                    this._disposed = true;
                    WinJS.Utilities.disposeSubTree(this.element);
                    if (this._submitButton && this._submitListener) {
                        this._submitButton.removeEventListener("click", this._submitListener);
                    }

                    WinJS.log("CustomDatePicker disposed");
                }
            }
        )
    });

    // Add the custom control to an existing <div> element on the page.
    var customDatePicker = new WinJS.Samples.CustomDatePicker(document.getElementById("dateDiv"));
}

function onclickDispose() {
    var listView = document.getElementById("listView").winControl;
    var datePicker = document.getElementById("datePicker").winControl;
    var dateDiv = document.getElementById("dateDiv").winControl;

    WinJS.Utilities.startLog();

    listView.dispose();
    WinJS.log("ListView disposed");
    datePicker.dispose();
     WinJS.log("DatePicker disposed");
    dateDiv.dispose();

    listView.innerHTML = "";
    datePicker.innerHTML = "";
    dateDiv.innerHTML = "";

    WinJS.Utilities.stopLog();
}

For additional code that shows how to use the dispose model, see the Dispose model sample.