Web Workers
Pierwszymi przeglądarkami, która mają zamiar zaoferować obsługę Web Workers w swoim silniku JavaScriptu są Mozilla Firefox 3.5 oraz Safari 4. Dla mnie osobiście jest to jedna z najciekawszych nowości nadchodzącej edycji Firefoksa.
Czym są Web Workers?
Termin Web Workers odnosi się do opracowywanego API, którego celem jest udostępnienie programistom aplikacji sieciowych możliwości uruchomienia pewnego kodu w osobnym wątku, dzięki czemu będzie możliwe zatrudnienie przeglądarki do wykonania skomplikowanego i czasochłonnego algorytmu bez obawy o zablokowanie interfejsu przeglądarki do czasu zakończenia działania skryptu.
Z samej definicji widać, że Workery idealnie nadają się do wykonywania skomplikowanych obliczeń. Przykładowymi - aczkolwiek banalnymi - przykładami mogą być operacje znajdowania kolejnych liczb pierwszych czy aproksymowanie liczby PI do coraz większej ilości miejsc po przecinku. Są to operacje dość czasochłonne, dlatego nie nadające się do uruchomienia w "głównym wątku" programu.
Kilka szczegółów API
Działanie Workerów opiera się o nowy obiekt Worker. Konstruktor obiektu oczekuje jednego argumentu w postaci URLa do skryptu, który ma zostać wykonany w osobnym wątku.
var worker = new Worker('skrypt.js');
worker.addEventListener('message', gotMessage, false);
Powyższy fragment kodu prezentuje sposób utworzenia nowego wątku, którego kod zawarty jest w pliku skrypt.js oraz "otwarcia" kanału komunikacji między wątkiem a jego twórcą. Osoby, które zetknęły się wcześniej z modułem Cross-Document Messaging, który wdrożono wraz z Firefoksem 3.0, zauważą zapewne, że komunikacja odbywa się poprzez wiadomości, które wątek może wysłać z użyciem metody postMessage.
Komunikacja w drugą stronę (obiekt -> wątek) też jest możliwa i także odbywa się z użyciem funkcji postMessage. Do funkcji można przekazywać także obiekty, o czym szerzej napiszę w rozdziale o zagrożeniach i ograniczeniach.
Pod-wątki
Z poziomu wątku można utworzyć kolejne pod-wątki. Jedynym ograniczeniem w tym miejscu jest, że kod pod-wątków musi być hostowany w tej samej domenie (tj. spełniać politykę same-origin) co rodzic, a ich URL - jeśli podany jako ścieżka względna - będzie ustalany relatywnie do lokalizacji wątku-twórcy, a nie dokumentu w ramach którego ma zostać uruchomiony.
Zabijanie wątków
Każdy z utworzonych wątków można w każdej chwili zakończyć. Wystarczy wywołać metodę terminate na instancji obiektu Worker.
worker.terminate();
Obsługa błędów
Jeśli kod wykonywany w ramach wątku wygeneruje błąd, można go bez problemu przechwycić, ponieważ wygenerowane zostanie zdarzenie error implementujące interfejs ErrorEvent, który zawiera 3 pola:
- message - komunikat błędu
- filename - plik, którego kod wygenerował błąd
- lineno - numer linii kodu, który wygenerował błąd
ErrorEvent nie bąbluje i można go anulować, żeby zapobiec domyślnej akcji. W tym celu należy wykonać na instancji obiektu zdarzenia wykonać dobrze znaną metodę preventDefault.
Importowanie skryptów i bibliotek
Workery mają dostęp do globalnej funkcji importScripts, za pomocą której można zaimportować do przestrzeni wątku inne skrypty i biblioteki. Funkcja oczekuje listy URLi (zera, jednego lub wielu) do skryptów, które należy zaimportować. Przykładowe wywołania funkcji mogą wyglądać:
importScripts('plik1.js', 'plik2.js', ...); // importuje cały zestaw plików podanych jako argumenty
importScripts(); // nic nie importuje
Pliki mogą być wczytywane przez przeglądarkę asynchronicznie, ale ich inicjacja odbywa się w takiej kolejności w jakiej przekazaliśmy do funkcji importScripts. W ten sposób mamy kontrolę nad ewentualnym procesem nadpisywania się danych z poszczególnych bibliotek. Funkcja kończy swoje działanie w momencie, gdy wszystkie biblioteki zostały zaimportowane i zainicjowane.
Zagrożenia i ograniczenia
Oczywiście, jak na wątki przystało, wprowadzenie ich do JavaScriptu niesie pewne zagrożenia i narzuca swoje ograniczenia.
Jeśli mowa o zagrożeniach, to przede wszystkim dotyczą one autorów rozszerzeń dla przeglądarki, ponieważ kod uruchomiony z poziomu rozszerzenia ma dostęp do sporej liczby interfejsów, które nie są przystosowane do pracy w wątku, co - przy nieuważnym programowaniu - może negatywnie odbić się na działaniu przeglądarki. Jak na razie, w dokumentacjach w serwisie MDC radzi się takim programistom przeglądanie źródeł danego interfejsu w poszukiwaniu przypuszczalnych problemów związanych z uruchomieniem kodu w wątku.
Na wątki uruchamiane w kontekście strony WWW narzucono kilka ograniczeń, przez co - wg autorów - praktycznie niemożliwe będzie wykonanie niebezpiecznych dla przeglądarki działań. Ograniczenia dotyczą głównie zablokowania dostępu do komponentów "non-thread safe", w szczególności do funkcji operujących na drzewie DOM dokumentu. Jeśli zajdzie potrzeba operowania na dokumencie, można z poziomu wątku wysłać komunikat do twórcy tego wątku, żeby ten dokonał stosownych operacji.
Komunikacja pomiędzy "twórcą" a wątkiem także narzuca pewne ograniczenia. Wcześniej napisałem, że poprzez postMessage można przekazywać do wątków obiekty. Ograniczenie polega na tym, że funkcja automatycznie przekonwertuje obiekt do łańcucha JSON, a format ten nie akceptuje funkcji ani cyklicznych referencji w procesie serializacji. Jak widać na tym przykładzie, nie ma szans, żeby przekazać do wątku referencji do np. węzła DOM czy jakiejś funkcji, która mogłaby próbować manipulować drzewem dokumentu.
Jako ciekawostkę można podać, że dozwolone są operacje wejścia/wyjścia poprzez obiekt XMLHttpReuest, ale z tą różnicą, że właściwości responseXML i channel obiektu zawsze będą ustawione na null.
Podsumowanie
Jak widać, uruchomienie fragmentu kodu JavaScript w osobnym wątku jest banalnie proste. Funkcjonalność ta niesie ze sobą spore możliwości, szczególnie, gdy aplikacje sieciowe zaczynają przetwarzać coraz większe dane (grafikę, dźwięk, wideo). Przypuszczam, że autorzy skryptów znajdą wiele zastosowań dla Workerów, które swoją pomysłowością niejedną osobę powalą na kolana.
Wracając jeszcze do przeglądarki Safari, która wraz z betą czwartej edycji udostępnia obiekt wątku roboczego, wykonuje kod tak, jakby był uruchomiony w głównym wątku aplikacji, co skutkuje zablokowaniem UI przeglądarki. Nightly build silnika Webkit, który testowałem (z dnia 13 maja 2009) także nie wprowadza zmian w tej kwestii. Miejmy nadzieję, że niedługo aspekt ten zostanie poprawiony.


Subskrybuj