Quickstart: Schedule tasks using WinJS

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

The Scheduler allows your Windows Runtime app using JavaScript to perform background work without disrupting the app's foreground tasks (like animation). This helps minimize a seemingly unresponsive or 'stuttering' experience for users and can possibly shorten startup time. This quickstart explains how to schedule prioritized background tasks using the Scheduler object in the Windows Library for JavaScript.

Note  For more examples and sample code that demonstrate how to use the Scheduler, see the HTML Scheduler sample.

 

Prerequisites

For this quickstart, you need to be able to create a basic Windows Runtime apps using JavaScript that uses WinJS in Windows 8.1 or Windows Phone 8.1. For help creating your first app, see Create your first Windows Runtime app using JavaScript.

Instructions

Create a new project by using the Blank App template

This article uses a basic Blank App template in Microsoft Visual Studio 2013. To create a new Blank App, do this:.

  1. Start Microsoft Visual Studio Express 2013 for Windows.

  2. From the Start Page tab, click New Project. The New Project dialog opens.

  3. In the Installed pane, expand Templates, JavaScript, and then Store Apps. Select the type of project (Windows, Windows Phone, or Universal) that you want to create. The available project templates forJavaScript are displayed in the center pane of the dialog.

  4. In the center pane, pick the Blank App project template.

  5. In the Name text box, type Scheduler demo.

  6. Click OK to create the project.

Add the HTML and CSS for the app

The sample app provided in this article uses a simple HTML interface that is laid out in a grid with two rows and two columns. To set up the HTML markup for the page and the Cascading Style Sheets (CSS) that are used to style the app, do this:

  1. Open default.html and insert the following HTML into the <body> element. This code should be a direct child of the <body> element. The HTML creates three <div> elements, used to display output from the JavaScript code.

    <div id="scheduler">
        <button id="getState">Get scheduler state</button><br />
    </div>
    <div id="counter">
        <h2>Counter</h2>
        <div id="counterOutput"></div>
    </div>
    <div id="state">
        <h2>Scheduler state</h2>
        <textarea id="stateResult"></textarea>
    </div>
    
  2. Open the default.css file (css/default.css) and replace the contents with the following code. This CSS arranges the HTML in the app in a simple 2 × 2 grid.

    body {
        display: -ms-grid;
        -ms-grid-columns: 500px 1fr;
        -ms-grid-rows: 500px 1fr
    }
    body * {
        margin: 20px;
    }
    
    #scheduler {
        -ms-grid-column: 1;
        -ms-grid-row: 1
    }
    
    #state {
        -ms-grid-column: 2;
        -ms-grid-row: 2;
    }
    
    #state textarea {
        width: 400px;
        height: 200px;
    }
    
    #counter {
        -ms-grid-column: 1;
        -ms-grid-row: 2;
    }
    
    #counter div {
        font-size: 20pt;
        color: aqua;
    }
    

Add the code to schedule jobs on the Scheduler

The Scheduler object in Windows Library for JavaScript (WinJS) uses a single universal queue with a priority-based scheduling policy. This allows you to specify tasks at varying levels of priority. The tasks are then prioritized against other tasks within the system.

Using the Scheduler.schedule method, you can queue up jobs on the scheduler. The schedule method accepts four parameters: work, priority, thisArg, and name.

  • The work parameter accepts a function that defines the work to be done. When the function is called, it is passed a single argument—a IJobInfo object. The IJobInfo object includes members for managing the execution of the job. The shouldYield property specifies whether the job should be paused for other, higher-priority work. The setWork method allows the job to cooperatively yield. It can be rescheduled to finish its work in the future when the scheduler determines it to be the right time, without the job losing its spot within its current priority level. The IJobInfo object also includes a job property that returns an IJob object, which contains details about the job itself.
  • The priority parameter, as the name implies, sets the priority of the job. It accepts a value from the Priority enumeration. For example, a high priority job is executed before a normal priority job, which is executed before a belowNormal priority job (and so on).
  • You can use the thisArg parameter of the schedule method to specify a context to run the job on. When the job is running, the function passed in for the work parameter can access the context through the this keyword.
  • The fourth parameter, name, provides a means of identifying the job within the scheduler's queue. This parameter allows you to identify tasks running on the scheduler when you're debugging or logging events.

In this next procedure, you create a job to run on the scheduler. This code schedules a low-priority job that counts from 1 through 100,000, updating the user interface as it counts. A counter variable, contained in the parent's lexical scope, maintains the state of the job. When a higher-priority job enters the queue, this job exits and reschedules its work on the scheduler.

  1. Open default.js and add the following code to the handler for the app.onactivated event (after the call to args.setPromise).

    var scheduler = WinJS.Utilities.Scheduler;
    
    // Define a low-priority, time-consuming, job for the scheduler
    // to execute. This job counts from 1 to 100K, displaying each
    // number that it counts.
    var counter = 0;
    var counting = function (jobInfo) {
    
        while (counter < Math.pow(10, 5)) {
    
            counter++;
            counterOutput.innerText = counter;
    
            // Force the job to pause if a higher priority job
            // comes into the queue, and resume the job after
            // the other job is complete. Note that you need to 
            // include the 'break' to exit the while block.
            if (jobInfo.shouldYield) {
                jobInfo.setWork(counting);
                break;
            }    
        }
    }
    
    // Schedule the counting job at a low-level priority.
    scheduler.schedule(counting, 
        scheduler.Priority.idle, 
        null,
        "counting")
    
  2. Add the following code to the event handler for the app.onactivated, below the code that you added previously. This code adds an event handler to the click event of the button that is declared in the HTML. The event handler schedules a new job called gettingState on the scheduler with a normal priority. Since the counting job has a priority of idle, the gettingState job preempts it because it has a normal priority.

    // Get the state of the scheduler. While the counting job
    // is running, this will display two jobs: this 'getting state'
    // job and the 'counting' job.
    var gettingState = function (jobInfo) {
        var currentTime = new Date();
        currentTime = currentTime.toLocaleTimeString();
    
        var state = scheduler.retrieveState();
        stateResult.value = currentTime + "\n\n" + state;
    }
    
    // When the button is clicked, display the current state of 
    // the scheduler to the user. This is scheduled as a 
    // normal-priority job so that the 'counting' job will yield to it.
    getState.addEventListener("click", function () { 
    
        scheduler.schedule( 
            gettingState, 
            scheduler.Priority.normal, 
            null, 
            "getting state");
    
    });
    
  3. Press F5 to run the sample and, with the counter running, click the Get scheduler state button. The output under Scheduler state looks something like this.

    9:11:52PM
    
    Jobs:
      *id: 3, priority: normal, name: getting state
       id: 2, priority: idle, name: counting
    Drain requests:
       None
    

    The output shows the jobs currently in the queue for the scheduler. The asterisk indicates the job that is currently running on the scheduler.

You can also preempt all other jobs on the scheduler by using the Scheduler.execHigh method. The execHigh method executes some code synchronously (it is done when execHigh returns) at the high priority level. This ensures that any asynchronous operations which are started during that execution have a high priority.

The following procedure changes the gettingState job so that it is scheduled using the execHigh method.

  1. Replace the click event handler for the getState button with the following code.

    // When the button is clicked, display the current state of 
    // the scheduler to the user. This is scheduled with the highest
    // priority, so the counting job yields to it.
    getState.addEventListener("click", function () {
    
        scheduler.execHigh(gettingState);
    
    });
    
  2. Press F5 to run the sample and with the counter running, click the Get scheduler state button. The output under Scheduler state now looks something like this.

    9:32:49PM
    
    Jobs:
       id: 2, priority: idle, name: counting
    Drain requests:
       None
    

    You see that the gettingState job again preempts the counting job. In this case, however, gettingState doesn't show up in the queue because it is executed immediately.

Here's the complete code for default.js.

(function () {
    "use strict";

    var app = WinJS.Application;
    var activation = Windows.ApplicationModel.Activation;
    var scheduler = WinJS.Utilities.Scheduler;

    app.onactivated = function (args) {
        if (args.detail.kind === activation.ActivationKind.launch) {
            if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
                // TODO: This application has been newly launched. Initialize
                // your application here.
            } else {
                // TODO: This application has been reactivated from suspension.
                // Restore application state here.
            }
            args.setPromise(WinJS.UI.processAll());

            // Define a low-priority, time-consuming, job for the scheduler
            // to execute. This job counts from 1 to 100K, displaying each
            // number that it counts.
            var counter = 0;
            var counting = function (jobInfo) {

                while (counter < Math.pow(10, 5)) {

                    counter++;
                    counterOutput.innerText = counter;

                    // Force the job to pause if a higher priority job
                    // comes into the queue, and resume the job after
                    // the other job is complete. Note that you need to 
                    // include the 'break' to exit the while block.
                    if (jobInfo.shouldYield) {
                        jobInfo.setWork(counting);
                        break;
                    }    
                }
            }

            // Schedule the counting job at a low-level priority.
            scheduler.schedule(counting, 
                scheduler.Priority.idle, 
                null,
                "counting")

            // Get the state of the scheduler. While the counting job
            // is running, this will display two jobs: this 'getting state'
            // job and the 'counting' job.
            var gettingState = function (jobInfo) {
                var currentTime = new Date();
                currentTime = currentTime.toLocaleTimeString();

                var state = scheduler.retrieveState();
                stateResult.value = currentTime + "\n\n" + state;
            }

            // When the button is clicked, display the current state of 
            // the scheduler to the user. This is scheduled as a 
            // normal-priority job so that the 'counting' job will yield to it.
            getState.addEventListener("click", function () {

                // Uncomment the following line of code to run the gettingState
                                                    // job at a 'normal' priority.
                //scheduler.schedule(gettingState,scheduler.Priority.normal, null, "getting state");
                scheduler.execHigh(gettingState);

            });
        }
    };

    app.oncheckpoint = function (args) {
        // TODO: This application is about to be suspended. Save any state
        // that needs to persist across suspensions here. You might use the
        // WinJS.Application.sessionState object, which is automatically
        // saved and restored across suspension. If you need to complete an
        // asynchronous operation before your application is suspended, call
        // args.setPromise().
    };

    app.start();
})();

Summary and next steps

In this quickstart, you learned how to schedule simple jobs and saw how the scheduler can preempt jobs in favor of higher-priority jobs.

When you looked at the output of the gettingState job, you probably noticed the Drain requests string. The scheduler can drain all high-priority jobs from the queue—that is, it can run all of the tasks in the universal queue for the Scheduler. Using this method, you trade responsiveness for throughput. It's best to use this method sparingly and only when responsiveness is not an issue (for instance, during app startup). For more info, see the Scheduler.requestDrain method.

This quickstart also didn't discuss how to get or execute promises on the scheduler or how to use owner tokens. For more info about those subjects, see the Scheduler object reference topics.

HTML Scheduler sample

JavaScript Web Workers sample