Ausführen von Code

Der Code in einer App definiert ihr Verhalten. Wenn Sie verstehen, wie die App diesen Code ausführt, können Sie die Leistung besser optimieren.

So wird Code in der App ausgeführt

Es gibt zwei Aspekte bei der Ausführung von Code in der App die besonders wichtig sind: App-Code ist ereignisgesteuert, und alle Komponenten verwenden den gleichen Thread.

  • Ereignisgesteuert

    Eine Windows Store-App, die auf die JavaScript-Plattform aufbaut, ist ereignisgesteuert. Dies bedeutet, dass der Code nur ausgeführt wird, wenn er durch ein Ereignis ausgelöst wird. (Die einzige Ausnahme besteht darin, dass Code auch bei der Analyse ausgeführt werden kann.)

  • Singlethread-Apps

    Eine Windows Store-App, die auf die JavaScript-Plattform aufbaut, ist eine Singlethread-App. Dies bedeutet, dass alle Komponenten der Plattform im gleichen Thread ausgeführt werden, sowohl die Benutzeroberfläche als auch der JavaScript-Code. Dabei kann jeweils nur ein Codeteil ausgeführt werden. Die einzige Möglichkeit, den Code in einem anderen Thread auszuführen, besteht in der Verwendung von Web-Workern. Web-Worker erstellen einen parallelen Thread, der nur für die Ausführung von JavaScript-Code verwendet wird. In Windows 8 werden Hintergrund-Apps weniger, Vordergrund-Apps hingegen mehr CPU-Zyklen zugeteilt, sodass deren Benutzeroberfläche flüssiger reagiert.

Die Plattform führt den Code für die einzelnen Ereignisse in der Warteschlange nacheinander aus, wie in der folgenden Abbildung dargestellt:

Die Ereigniswarteschlange

Die Abbildung zeigt, dass die Plattform zurzeit einen Fingereingabe-Ereignishandler verarbeitet, während ein Zeitgeberereignis, ein postMessage-Ereignis und ein weiteres Zeitgeberereignis in der Warteschlange auf die Verarbeitung warten. Wenn der Code für einen Ereignishandler ausgeführt wird, kann die Plattform nicht auf neue Ereignisse reagieren. Wenn ein neues Ereignis eintritt, während der Code für den Fingereingabe-Ereignishandler ausgeführt wird (z. B. wenn der Benutzer ein anderes Element auf dem Bildschirm berührt), kann der neue Fingereingabe-Ereignishandler erst ausgeführt werden, nachdem der aktuelle Handler beendet wurde. Falls die Ausführung des Codes für den ersten Fingereingabe-Ereignishandler viel Zeit in Anspruch nimmt, tritt eine Verzögerung zwischen der Handlung des Benutzers und der Reaktion der App auf. Dies wird als lange Reaktionszeit wahrgenommen.

Es gibt Möglichkeiten, um solche Leistungsprobleme zu vermeiden. In den nächsten Abschnitten werden einige dieser Möglichkeiten erläutert.

Keinen Code mit langer Ausführungszeit ausführen

Wenn der Benutzer mit der App interagiert, indem er den Bildschirm berührt, und Sie die Interaktion in einem Ereignis behandeln, kann die App erst auf die nächste Benutzerinteraktion reagieren, nachdem der Code für den ersten Ereignishandler abgeschlossen wurde. Verwenden Sie die folgenden Verfahren zum Ausführen von Code, um sicherzustellen, dass die App weiterhin reagieren kann:

  • Auslagern der Codeausführung in einen anderen Thread mit Web-Workern

    Wenn die Ausführung des Codes viel Zeit in Anspruch nimmt, lagern Sie diesen in einen Web-Worker aus. Mit Web-Workern bewirken Sie, dass ein bestimmter Teil des Codes einer App in einem anderen Thread, nicht dem Hauptthread ausgeführt wird. Dies ist z. B. sinnvoll, um in einem Spiel, bei dem der Benutzer gegen den Computer spielt, den Spielzug des Computers zu berechnen. Normalerweise nimmt die Ausführung des Codes zur Berechnung des nächsten Zugs viel Zeit in Anspruch. Wenn dieser Code im Hauptthread ausgeführt wird, kann die App erst auf weitere Benutzereingaben reagieren, nachdem die Ausführung des Codes abgeschlossen wurde. Stattdessen kann die App die Logik in einen Web-Worker auslagern. Dabei wird der Code in einem anderen Thread ausgeführt, und der Hauptthread wird benachrichtigt, nachdem die Berechnung abgeschlossen wurde.

    Tipp  Wenn ein ressourcenintensiver (zeitaufwändiger) Vorgang im Hauptthread der App ausgeführt wird, können keine anderen Ereignisse verarbeitet werden. Lagern Sie ressourcenintensive Aufgaben daher in einen Web-Worker aus. Einige Vorgänge, z. B. das Berechnen des nächsten Spielzugs, können viel Zeit in Anspruch nehmen und die Reaktion auf andere Ereignisse verzögern.

  • Ausführen von Code im UI-Thread mit "setImmediate"

    Die setImmediate-Funktion kann ebenfalls dazu beitragen, dass die App stets flüssig reagiert. Legen Sie mit dieser Funktion eine Rückrufmethode fest, die ausgeführt wird, nachdem die Plattform die Verarbeitung aller Ereignisse abgeschlossen und die Anzeige aktualisiert hat.

    Die setImmediate-Funktion stellt eine Möglichkeit dar, Code im UI-Thread auszuführen, wenn keine anderen Ereignisse auf die Verarbeitung warten. Sie verhindert jedoch nicht, dass während der Ausführung des Rückrufs neue Ereignisse eintreten. Verwenden Sie daher die setImmediate-Funktion nicht zum Ausführen von ressourcenintensivem Code. Nutzen Sie sie nur für kurze Vorgänge, die keine Ereignisse beeinträchtigen, die sich möglicherweise bereits in der Warteschlange befinden.

    Obwohl setImmediate zu einer effizienteren Ausführung des App-Codes im UI-Thread beiträgt, kann die übermäßige Verwendung der setImmediate-Funktion zu Leistungsproblemen führen. Zwischen setImmediate-Aufrufen in der Warteschlange könnte die Ausführung an den Host übergeben werden, sodass aufwendige Vorgänge, z. B. ein Layoutdurchlauf, stattfinden könnten. Aus diesem Grund empfehlen wir, zusammengehörige Aktualisierungen, z. B. UI-Aktualisierungen, in einem einzigen setImmediate-Aufruf stapelweise zu verarbeiten.

  • Vermeiden der Erstellung von Timern mit einer Ausführungszeit von weniger als 10 ms

    Wenn Sie für eine Timerfunktion (z. B. setTimeout) eine Zeit von weniger als 10 ms angegeben, muss das System für den Timer zusätzliche Binärdateien laden. Durch das Laden zusätzlicher Binärdateien kann die Leistung beeinträchtigt werden. Vermeiden Sie also nach Möglichkeit das Erstellen von Timern, die für weniger als 10 ms ausgeführt werden.

Verwenden von asynchronen APIs

Zur Verbesserung der Reaktionsfähigkeit der App stellt die Plattform asynchrone Versionen vieler APIs bereit. Eine asynchrone API wird im Hintergrund ausgeführt und benachrichtigt die App nach Abschluss durch Auslösen eines Ereignisses. Wenn Sie eine API aus dem Hauptthread aufrufen, verwenden Sie die asynchrone Version, sofern diese verfügbar ist.

Ein Beispiel für eine häufig verwendete hilfreiche asynchrone API ist XMLHttpRequest. Hierbei handelt es sich um eine Netzwerk-API. Mit dieser API kann eine App eine Anforderung an einen Remoteserver einleiten. Die Zeit, die zum Abschließen der Anforderung benötigt wird, ist nicht bekannt. In diesem Beispiel wird ein Vorgang zum Öffnen asynchron ausgeführt, indem true für die asynchrone Ausführung an die XMLHttpRequest.open-Funktion übergeben wird.


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);


Wenn der Code im Beispiel ausgeführt wird, startet der Hauptthread eine Hintergrundanforderung an die angegebene URL und fährt dann mit dem Ausführen von weiterem Code fort. Nach Abschluss der Anforderung wird das load-Ereignis ausgelöst. Die App kann das Ereignis behandeln, um die XMLHttpRequest-Ergebnisse zu verarbeiten.

Wenn im Beispiel stattdessen der Wert false an die XMLHttpRequest.open-Funktion übergeben wird, blockiert das JavaScript-Modul die Ausführung von anderem Code, bis XMLHttpRequest abgeschlossen wurde.

Tipp  Wenn Sie Code im Hauptthread ausführen, verwenden Sie asynchrone APIs, sofern diese verfügbar sind. Wenn Sie eine synchrone API verwenden, wird anderer Code im Hauptthread erst wieder ausgeführt, nachdem die API abgeschlossen wurde. Die App kann daher nicht auf neue Ereignisse reagieren, während die API ausgeführt wird.

Abbrechen von laufenden Anforderungen in geeigneten Situationen

Anforderungen, z. B. Netzwerkanforderungen, können einfach eingeleitet werden. Häufig werden sie dann jedoch vergessen. Es kommt oft vor, dass eine App eine Netzwerkanforderung einleitet, diese jedoch nicht abgebrochen wird, wenn sie nicht mehr benötigt wird.

Dies ist häufig der Fall, wenn Apps den Vertrag für "Suche" implementieren und auf das Änderungsereignis für das Suchfeld warten. Einige Apps müssen eine Netzwerkanforderung durchführen, um Ergebnisse für den Benutzer abzurufen, und leiten die Anforderung ein, wenn das Ereignis ausgelöst wird. Häufig gerät dabei jedoch die vorherige Suchanforderung in Vergessenheit. Falls es eine vorherige Suchanforderung gab, wird diese nicht mehr benötigt, und sie sollte abgebrochen werden.

Die meisten ressourcenintensiven APIs, z B. FileReader oder XMLHttpRequest, stellen eine Möglichkeit zum Abbrechen des Vorgangs bereit. Durch das Abbrechen kann die App die Ressourcen zurückgewinnen, die andernfalls zum Abschließen des Vorgangs verwendet worden wären.

Verwenden des Planers

Mit der in Windows 8.1 eingeführten Planer-API können Sie die Priorität der Aufgaben (auch als Arbeitsaufgaben oder Aufträge bezeichnet) festlegen und die Aufgaben verwalten. Dadurch können Sie Windows Store-Apps erstellen, die die Ressourcen des Systems effizienter nutzen und schneller auf die Aktionen der Benutzer reagieren.

Um die Planer-API zu verwenden, rufen Sie die Funktion schedule mit Angabe einer Aufgabe und einer Priorität auf. Hier sehen Sie ein Beispiel.


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.
*/

Die schedule-Funktion gibt ein Objekt zurück, das die IJob-Schnittstelle implementiert. Über diese Schnittstelle können Sie die geplante Aufgabe anhalten, fortsetzen und abbrechen.

Weiteren Beispielcode zur Verwendung der schedule-Funktion finden Sie unter Beispiel zur HTML-Planung.

Verwalten der Arbeitsspeichernutzung

Das in Windows 8.1 eingeführte Dispose-Modell ermöglicht Elementen und Steuerelementen, Ressourcen am Ende ihrer Lebensdauer freizugeben, um Arbeitsspeicherverluste zu verhindern. Ein Element oder Steuerelement kann es optional implementieren. Steuerelemente der Windows-Bibliothek für JavaScript 2.0 mit Ressourcen, die freigegeben werden können (z. B. ItemContainer, SearchBox und SettingsFlyout), implementieren dieses Muster. Um dieses Modell zu nutzen, rufen Sie die Dispose-Methode des Steuerelements auf (z. B. ItemContainer.dispose, SearchBox.dispose, SettingsFlyout.dispose), wenn es nicht mehr benötigt wird (z. B. wenn der Benutzer die Seite verlässt oder die App beendet).

Im folgenden Beispiel wird die Verwendung des Dispose-Modells veranschaulicht. Hierbei wird ein benutzerdefiniertes Datumsauswahl-Steuerelement erstellt und angezeigt. Dieses Steuerelement enthält ein ListView, a DatePicker, ein button und ein div. Außerdem wird ein separates div mit zwei button-Steuerelementen deklariert. Durch Klicken auf die Schaltfläche Steuerelement erstellen wird eine neue Instanz des benutzerdefinierten Datumsauswahl-Steuerelements erstellt und angezeigt. Durch Klicken auf Löschen werden alle Ressourcen des Steuerelements freigegeben. Die Steuerelemente können nicht mehr verwendet werden und reagieren nicht mehr.



<!-- ... -->
<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();
}


Weiteren Beispielcode zur Verwendung des Dispose-Modells finden Sie unter Beispiel für Dispose-Modell.

 

 

Anzeigen:
© 2014 Microsoft