HTML5: Web Workers

html5 Web Worker

html5 Web Worker

Технология HTML5 Web Workers предоставляет веб-приложениям возможности фоновой обработки в виде отдельных потоков, что позволяет JavaScript-приложениям в полной мере использовать преимущества многоядерных процессоров. Кроме того, использование потоков позволяет избежать назойливых предупреждений о долгом выполнении скрипта, которые появляются если скрипт выполняется дольше нескольких секунд.

Введение

До появления стандарта HTML5, все Web-сценарии выполнялись в одном потоке. Подразумевается, что и загрузочные Web-сценарии, выполняющиеся прямо в процессе загрузки Web-страницы, и обработчики событий, и всё остальное выполнялось в одном потоке.

То есть, представьте. Вы жмёте на кнопку, начинаются вычисления, которые вешают ваш поток, и вы ничего не может поделать, кроме как ждать. Как уже говорил, выполнение всех Web-сценариев производится в главном потоке выполнения Web-обозревателя, как и действия пользователя: прокрутка Web-страниц, переходов по гиперссылкам и др. И одновременно выполнять Web-сценарий и обрабатывать действия пользователя Web-обозреватель не может. Поэтому, пока выполняется наш Web-сценарий, Web-обозреватель не сможет обработать щелчок на кнопке или что-то другое.

Конечно, делали костыли, эмулировали многопоточность с помощью setInterval и setTimeout. Но их использование и ряд ограничений накладывал.

Web Workers: полноценная многопоточность

Web Worker — Web-сценарий, работающий в параллельном потоке. Этот поток имеет более низкий приоритет, нежели главный, поэтому в любом случае не сможет нарушить работу; следовательно, задачи обработки действий пользователя и реакции на них всегда будут выполняться незамедлительно. То есть, по сути, фоновые потоки.

Поддержка спецификации браузерами

БраузерОписание
ChromeПоддерживается в версии 3 и выше
FireFoxПоддерживается в версии 3.5 и выше
Internet ExplorerПоддерживается в версии 10 и выше
ChromeПоддерживается в версии 3 и выше
OperaПоддерживается в версии 10.6 и выше
SafariПоддерживается в версии 4 и выше

Доступные действия

Что могут Web Workers и к чему имеют доступ:

  • Доступ к объекту navigator
  • Доступ к объекту location (read-only)
  • Доступ к XMLHttpRequest
  • Создавать setTimeout()/clearTimeout()
  • Создавать setInterval()/clearInterval()
  • Доступ к кэшу приложения
  • Может выполнять Ajax запросы
  • Может подгружать другие скрипты с помощью метода importScripts()
  • Может оздавать дочерние потоки

К чему Web Workers доступа не имеют:

  • DOM (ибо не потоко-безопасно)
  • window
  • document
  • parent

Достоинства

  • Практически любой Web-сценарий можно запустить в качестве Web Worker. Разве что необходимо ещё дописать методы по отправке сообщений.
  • Поскольку Web-сценарий выполняется в параллельном потоке, он никак не может нарушить работу самого Web-обозревателя и всех Web-сценариев, выполняющихся в главном потоке. Песочница :3

Недостатки

  • Web Worker не имеет доступа к window.document. Это означает, что у потоков отсутствует возможность непосредственного доступа к веб-странице и DOM.
  • Web Worker не может обращаться к переменным, объявленным в общем потоке. Ну, это очевидно.

То есть, по сути, недостатков то и нет. Если результат выполнения потока как-то надо отобразить на странице, то по получении сообщения от потока, можно в главном потоке соответствующие изменения внести. Что касается глобальных переменных…

Глобальные переменные — зло

В своё время много намучился с потоками в Win API под C++. К счастью, W3C думает о программистах, поэтому работать с потоками в HTML5 одно удовольствие.

Применение

Кто с Win API работал, знает о дилемме использования потоков. Порой, их использование только увеличивает по времени выполнение программы. Всё дело во времени на создание потока и переключение контекста.

Так что потоки больше используются для интерактивности, нежели для ускорения. Конечно, если вам необходимо выполнить немало фоновых вычислений, то использование Web Workers очень удачное решение.

В качестве другого примера использования Web Workers можно привести приложение, которое получает сообщения с сервера новостей и размещает их на основной веб-странице по мере их получения с сервера. Для связи с сервером могут использоваться веб-сокеты или серверные события.

Программный интерфейс HTML5 Web Workers

Переходим к самом главному — использованию потоков. Принцип работы в целом очень прост. Вы создаёте объект Worker и передаёте ему JavaScript файл, который необходимо выполнить. Настраиваем обработчики сообщений и ошибок, посылаемых потоков. Для передачи данных используем метод postMessage().

Проверка поддержки в браузере

Прежде, чем использовать Web Workers, следует убедиться, что браузер предоставляет необходимую поддержку. HTML5 уже довольно давно вышел, так что, большинство юзеров сидят под браузерами с поддержкой этой технологии. Но мало ли, может понадобиться проверять.

function testWW(){	
  if(typeof(Worker) !== "undefined") {
    //alert('WW поддерживается');
  }
}

//или
function testWW2(){	
  if (!!window.Worker) {
    //alert ('Работает!');
  }
}

Загрузка и выполнение дополнительного JS-кода

Приложение, в состав кторого входит несколько JavaScript-файлов, может содержать элементы <script>, осуществляюшие синхронную загрузку JS-файлов по мере загрузки страницы. Но поскольку потоки Web Workers не имеют доступа к объекту document, можно использовать другой механизм, обеспечивающий синхронное импортированние JavaScript-файлов изнутри потоков:

importScripts("help.js");

Импортируемый файл просто грузится в существующий поток и выполняется в нём. Одним вызовом метода можно импортировать сразу несколько сценариев. В этом случае сценарии выполняются в той очерёдности, в которой они указаны:

importScripts("help1.js", "help2.js");

Создание потоков Web Workers

Потоки Web Workers инициализируются URL-адресом JavaScript-файла, в котором код потока, подлежащий выполнению. Этот код устанавливает обработчик событий и обменивается сообщениями с породившим его сценарием. Для запуска:

worker = new Worker ("путь к скрипту");

В качестве единственного параметра конструктору передаётся строка с интернет-адресом файла Web-сценария, хранящего код Web Worker.

Обмен сообщениями с потоками

После того как поток создан, с ним можно обмениваться сообщениями, используя для этого метод postMessage(). С помощью этого метода можно пересылать большинство JS-объектов.

Прекращение выполнения потоков

Потоки Web Workers не могут самостоятельно прекратить работу: остановить их может лишь страница, которая их запустила. По аналогии с WIN API. Там необходимо было дескрипторы потоков закрывать и т.д. и т.п. Если в потоке больше нет необходимости, следует освободить занимаемые ресурсы. Кроме того, в некоторых случаях, если поток выполняется слишком долго, юзер может захотеть преждевременно прекратить его выполнение. Для прекращения работы потока, следует вызвать метод terminate(). Поток, выполнение которого прекращено, перестаёт отвечать на сообщения и выполнять любые другие вычисления. Перезапустить поток нельзя, можно лишь создать новый.

Пример реализации простейшего Web Worker

Как пример разработаем приложение, где по нажатию кнопки запускается поток, который что-то там вычисляет, а потом возвращает результат вычислений. Мне сейчас лень голову ломать над примером алгоритма. Пускай внутри потока по таймауту отправляются результаты вычислений. Ну, или скажем, по таймату происходит запрос к серваку на получение последних новостей, а затем происходит возврат новости в основной поток, где она и публикуется на странице.

То есть, у нас есть скрипт, выполняющийся в потоке. Пускай webworker.js.

// Получаем сообщение из основного потока 
onmessage = function(e) {
  setInterval(messageByTimeout, 3000,e.data);
};

function messageByTimeout(e){
  // Отправляем сообщение в основной поток
  postMessage('Прокнул поток №'+e);
}

На странице у нас 2 кнопки. Одна запускает рабочий поток новый, вторая завершает все текущие потоки. Сразу весь код приведу.

//массив (пул) потоков
workers  = new Array();

//вызывается при нажатии на кнопку запуска потока
function startCalc(){
  //запуск потока
  workers[workers.length] = new Worker('webworker.js');

  //подписка на обработчик, перехватывающий сообщения об ошибках
  workers[workers.length-1].addEventListener("error", errorHandler, true);

  //подписка на обработчик, перехватывающий сообщения, направляемые потоком
  workers[workers.length-1].addEventListener("message", messageHandler, true);

  //отправка сообщения потоку c аргументами (в данном случае ID потока)
  workers[workers.length-1].postMessage(workers.length-1);
}

//вызывается при нажатии на кнопку остановки
function stopCalc(){
  for(i=0;i<workers.length;++i)
    workers[i].terminate();

  workers  = new Array();
}

//обрабочик сообщений от потока
function messageHandler(e){
  //в консоль
  console.log(e.data,e);

  //или на страницу
  document.getElementById('result').innerHTML +=e.data+'</br>';
}

//обработчик ошибок
function errorHandler(e){
  console.log(e.message,e);
}

Если хотите наглядно увидеть, как это работает, понаимайте на кнопочки снизу.

Результат:

Если два раза нажать на кнопку запуска, то создадутся два потока. После двух сообщений от каждого потока жмём кнопку остановки, результат будет таким.

Результат выполнения Web Workers