Visit Mozilla.org

The Thread Manager

From MDC

This article covers features introduced in Firefox 3


The Thread Manager, introduced in Firefox 3, offers an easy to use mechanism for creating threads and dispatching events to them for processing.

Contents

[edit] Interfaces

There are several interfaces that provide threading support:

nsIThreadManager
The Thread Manager itself lets you create threads.
nsIThread
The nsIThread interface encapsulates an operating system thread, providing easy cross-platform access to multithreading in your code.
nsIThreadPool
A thread pool provides a limited set of worker threads. When you dispatch an event to the pool, the pool selects an available worker thread to process the event.
nsIThreadInternal
A subclass of nsIThread that is implemented by the XPCOM thread object to add support for observing dispatch activity on a thread.
nsIThreadObserver
Provides the ability to monitor a thread, to receive notifications when events are dispatched to it and when they're finished being processed.
nsIThreadEventFilter
This interface is used by the pushEventQueue() method in nsIThreadInternal to allow event filtering.

[edit] Using the Thread Manager

To use the Thread Manager, you need to encapsulate each thread's working code in an nsIRunnable XPCOM object. You can write the entire object in JavaScript, though, and it's not hard to do.

Note: The DOM is not thread safe. You must not access the DOM or user interface from background threads. Doing so will likely crash.

In this section, we'll look at a simple example.

[edit] The background thread

First, we need an XPCOM object to handle the work to be done in the background thread:

var workingThread = function(threadID, number) {
  this.threadID = threadID;
  this.number = number;
  this.result = 0;
};

workingThread.prototype = {
  run: function() {
    try {
      // This is where the working thread does its processing work.
      
      for (var i = 0; i<= this.number; i++) {
        this.result += i;
      }
      
      // When it's done, call back to the main thread to let it know
      // we're finished.
      
      main.dispatch(new mainThread(this.threadID, this.result),
        background.DISPATCH_NORMAL);
    } catch(err) {
      Components.utils.reportError(err);
    }
  },
  
  QueryInterface: function(iid) {
    if (iid.equals(Components.interfaces.nsIRunnable) ||
        iid.equals(Components.interfaces.nsISupports)) {
            return this;
    }
    throw Components.results.NS_ERROR_NO_INTERFACE;
  }
};

The constructor for this thread saves a thread ID and a number into local variables, and sets a result variable to 0. These values will be used when the thread is executed.

There are two methods on the object aside from the constructor:

run()
The run() method is called when the nsIThread interface's dispatch() method is called. This is the routine that does the actual work in the background thread. In this case, we're computing the sum of all numbers from 0 through this.number. When the computation is complete, we get access to the main thread using the mainThread object and dispatch a callback to it to share the results.
QueryInterface()
Since threads' XPCOM objects need to handle the nsIRunnable interface, we need to respond correctly when this method is called to ask if our object handles that interface.

[edit] The main thread

The XPCOM object that handles the main thread is used as a callback from the background task. Its run() method is called when the background thread wishes to let the user know the result of its computations. This is necessary because background threads can't touch the user interface, so they need to ask the main thread to do it.

var mainThread = function(threadID, result) {
  this.threadID = threadID;
  this.result = result;
};

mainThread.prototype = {
  run: function() {
    try {
      // This is where we react to the completion of the working thread.
      alert('Thread ' + this.threadID + ' finished with result: ' + this.result);
    } catch(err) {
      Components.utils.reportError(err);
    }
  },
  
  QueryInterface: function(iid) {
    if (iid.equals(Components.interfaces.nsIRunnable) ||
        iid.equals(Components.interfaces.nsISupports)) {
            return this;
    }
    throw Components.results.NS_ERROR_NO_INTERFACE;
  }
};

The run() method here simply displays the output to the user using an alert box.

[edit] Putting it all together

To actually use the Thread Manager to do these computations in the background, we first need to create the nsIThread object to run the workingThread task on:

var background = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0);

We also need to get a reference to the main thread's nsIThread:

var main = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;

Once we have that information, we can dispatch a task to the background thread:

background.dispatch(new workingThread(1, 5000000), background.DISPATCH_NORMAL);

This starts the background thread running, computing the sum of all the numbers between 0 and 5,000,000. When the work is complete, the main thread's run() method is called to share the result with the user. In the meantime, the main thread can continue about its business, doing whatever it needs to do (such as respond to user interaction).