A common piece of functionality that extension developers wish to do is to create a thread to do work in without blocking the application. For pure-Javascript extensions, there are a few mechanisms to achieve this with. nsIThreadManager is one mechanism. Unfortunately, the syntax is a bit arcane, and it's burdened by some fairly strict restrictions, such as not being able to touch any UI (including the window, DOM, etc.).
Fortunately, Javascript1.7 introduced the concept of Iterators & Generators. We can capitalise on generators to create "pseudoThreads" in a much simpler, less restrictive, way than using nsIThreads. Songbird utilises this concept of pseudoThreads in a few places, such as the web playlist scraper, and in the Concerts & ♪Photo extensions.
yield statement is encountered. What this means is that when you have a function like:
function myGenerator() {
while (var i=0; i<100; i++) {
dump("hello world: " + i + "\n");
yield i;
}
}
Your first call of myGenerator() will result in "hello world: 0" being dumped to the console, and myGenerator returning a value of 0. Your second call to myGenerator() will result in "hello world: 1" and a return value of 1. Your third call will result in "hello world: 2" and a return value of 2, and so on so forth.One problem that is fairly common is that developers will try to do something that can be pretty CPU intensive. For Concerts & the ♪Photo extensions, they try to enumerate every media item in the library and get/set properties. When these operations run, the respective loops and enumerators they use consume the application's attention, and won't relinquish it to allow it to process other events in Songbird... such as events for redrawing or painting the application window, thus giving the user the appearance of Songbird having frozen or locked up.
For instance, to use a real world example, ♪Photo originally had the following code:
function triggerScan(list) {
var artists = list.getDistinctValuesForProperty(SBProperties.artistName);
while (artists.hasMore()) {
var artistName = artists.getNext();
// do some long complicated bit of work
}
var albums = list.getDistinctValuesForProperty(SBProperties.albumName);
while (albums.hasMore()) {
var albumName = albums.getNext();
// do even more longer crazier complicated bit of work
}
}
Don't worry too much about what this code does (since obviously the example is incomplete), but basically it would, given a playlist, enumerate all the artists in that playlist and fetch artist artwork for each artist. Once it had that, it would do the same, except for album artwork. Suffice to say, these were rather length and time intensive calls that, for a large library, would give the appearance of Songbird having frozen.
Fortunately, pvh put together a quick shortcut called pseudoThread. In a nutshell, pseudoThread is the following:
function pseudoThread(gen) {
var thisGen = this;
var callback = {
observe: function(subject, topic, data) {
// we are only interested in timer callbacks, not other messages.
if (!topic == "timer-callback") return;
try {
gen.next();
} catch (e) {
threadTimer.cancel();
threadTimer = null; // break XPCOM cycle
gen.close();
// a StopIteration message exception indicates a normal generator shutdown
if (!(e instanceof StopIteration)) {
Components.utils.reportError(e);
}
};
}
}
var threadTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
threadTimer.init(callback, 0, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
}
If you include the above Javascript code in your add-on, you'll have a super-quick way to start pseudoThreading your JS code. What pseudoThread does is wrap a given generator function (more on this in a bit) with a nsITimer (a repeating timer that fires repeatedly, in the above case: every "0" ms). Remember that with generators, state is remembered across invocations... so this effectively causes your generator to be called repeatedly every time it hits a yield statement until your generator function either throws an exception or returns without a yield.
The second step is converting your function into a generator. Here are the steps required for doing that:
function triggerScan(list) {
var artists = list.getDistinctValuesForProperty(SBProperties.artistName);
while (artists.hasMore()) {
var artistName = artists.getNext();
// do some long complicated bit of work
}
yield;
var albums = list.getDistinctValuesForProperty(SBProperties.albumName);
while (albums.hasMore()) {
var albumName = albums.getNext();
// do even more longer crazier complicated bit of work
yield;
}
}
Any time you yield, you give the rest of the application an opportunity to process all the messages that have queued up. You should yield regularly during any potentially long processes. Here, we're assuming that the artists loop will never be so slow that the user sees the app as "hung" while it is running, but that the slower albums loop will take longer. Where should you put yours? You can either use the Venkman JS Profiler to help find slow places, or you can just experiment and find the places that go slowly. Be sure to test the worst, slowest possible situation you can imagine! Remember: your users are out to get you.
pseudoThread(triggerScan(list));
function a() {
while(true) {
b();
}
}
function b() {
for (var i = 0; i < 100; i++)
yield i;
}
pseudoThread(a());
The "a()" function does not contain a yield keyword, so it cannot be driven by the pseudoThread. Similarly, the b() function is not being called with generator-style syntax, so it is being restarted each time. If this doesn't make sense to you, think about it more, and read the Iterators and Generators documentation. If it still doesn't make sense, come into #songbird.| Images 0 | ||
|---|---|---|
| No images to display in the gallery. |