Опубликован: 15.05.2013 | Доступ: свободный | Студентов: 265 / 9 | Длительность: 24:25:00
Специальности: Системный архитектор
Лекция 14:

Ключевые концепции компонентов WinRT

< Лекция 13 || Лекция 14: 12345 || Лекция 15 >

Рабочие процессы JavaScript

При работе исключительно с JavaScript, рабочие процессы – это тот способ, с помощью которого можно передать исполнение кода в другие потоки. Главное, что нужно здесь понять – это то, что взаимодействие между главным приложением (потоком пользовательского интерфейса) и рабочими процессами происходит посредством отдельных сообщений postMessage и с посощью связанного с ними события message. Таким образом, рабочие процессы не похожи на компоненты, в которых вы можете просто вызывать методы и получать результат их выполнения. Если вы хотите вызвать метод с конкретными аргументами внутри рабочего процесса, вы должны выполнять подобный вызов с помощью сообщения postMessage, которое содержит необходимые значения. С другой стороны, функция, которая вызвана внутри рабочего процесса, возвращает результат в главное приложение, так же вызывая postMessage.

Например, в упражнении "Image Manipulation", я поместил функцию countFromZero в js/worker_count.js вместе с обработчиком сообщения, который служит как простой диспетчер метода:

onmessage = function (e) {
switch (e.data.method) {
:
case "countFromZero"
countFromZero(e.data.max, e.data.increment);
break;

default:
break;
}
};

function countFromZero(max, increment) {
var sum = 0;
max = 10;

for (var x = 0; x < max; x += increment) {
sum += x;
}

postMessage({ method: "countFromZero", sum: sum });
}

Когда запускается этот рабочий процесс, единственный код, который исполняется, это назначение onmessage. Когда обработчик получает подходящее сообщение, он запускает функцию countFromZero, которая в ответ отправляет результаты своей работы. Другими словами, настройка рабочего процесса означает конверсию вызовов методов и результатов в сообщения.

Вызов данного метода из приложения теперь выглядит так:

var worker = new Worker('worker_count.js');
worker.onmessage = function (e) { //e.data.sum это результат }

//Вызов метода
worker.postMessage({ method: "countFromZero", max: 1000, increment: .00005 });

Имейте в виду, что сообщения postMessage – это асинхронные операции, таким образом, нет конкретных гарантий того, как быстро эти сообщения будут доставлены рабочему процессу или приложению. Более того, когда рабочий процесс создан, он не может приступить к работе до тех пор, пока исполняется скрипт (как когда вы вызываете setImmediate). Это означает, что рабочие процессы не особенно хорошо подходят для асинхронных операций, которые вы хотите запустить так быстро, как только возможно и от которых вы хотите получить результат, как только он будет готов. По этой причине, рабочие процессы лучше подходят для сравнительно больших операций и выполнения текущей работы. Маленькие, с высокой скоростью отклика, высокопроизводительные подпрограммы лучше размещать в WinRT-компонентах.

Механизм postMessage так же не лучшим образом подходит для объединения нескольких асинхронных операций в цепочку, что мы легко можем реализовать с помощьюpromise-объектов, которые поступают из API WinRT. Честно говоря, я не хочу даже начинать думать о подобном коде! Я предпочитаю вместо этого задаваться вопросом о том, есть ли способ, с помощью которого мы можем эффективно заключить механизм сообщений рабочего процесса в оболочку promise-объекта, таким образом мы сможем обращаться с асинхронными операциями одинаково, несмотря на особенности их реализации.

Чтобы это сделать, нам нужно найти способ получения результата из обработчика worker.onmessage и передачи его в обработчик завершения promise-объекта. Для того, чтобы это реализовать, мы используем некоторый код в главном приложении, который, на самом деле, выполняет ту же функцию, что и уровень проекции JavaScript, используемый для превращения WinRT API в promise-объекты

// Это переменная функции, которую мы подключаем. 
var workerCompleteDispatcher = null;

var promiseJS = new WinJS.Promise(function (completeDispatcher, errorDispatcher,
progressDispatcher) {	
workerCompleteDispatcher = completeDispatcher;	
});	

// Здесь рабочий процесс должен быть создан и сохранен в переменной 'worker' variable
// Прослушивание событий рабочего процесса
worker.onmessage = function (e) {	
if (workerCompleteDispatcher != null) {	
workerCompleteDispatcher(e.data.sum);
}	
}	
promiseJS.done(function (sum) {
// Вывод данных рабочего процесса JavaScript 
});

Первое, что здесь нужно понять, это то, что, на самом деле, выполняет promise-объект, и как promise-объект отделен от асинхронной операции. (Так и должно быть, так как API и компоненты WinRT ничего не знают о promise-объектах). На самом деле, promise-объект – это лишь инструмент для управления множеством функций-прослушивателей от имени асинхронной операции, такой, как наш рабочий процесс. То есть, когда асинхронная операция обнаруживает определенные события, а именно, события завершения, ошибки, прогресса операции, она хочет сообщить об этом тому, кто выразил интерес к этим событиям. Этот кто-то делает это путем вызова методов then или done promise-объектов и предоставляя один или большее количество обработчиков.

В методах then или done, все, что реально делают promise-объекты, это сохраняют указанные функции в списке (если только они не знают, что асинхронная операция уже завершана, в подобном случае они могут просто вызывать функцию обработки завершения или ошибки немедленном). Именно поэтому вы можете вызвать then или done много раз для того же самого promise-объекта – такие вызовы просто добавляют ваши обработчики завершения, ошибки и прогресса операции в соответствующий список внутри promise-объекта. Конечно, эти списки бесполезны, если нет какого-то способа вызвать обработчики, которые в них хранятся. Для этой цели у promise-объекта есть три собственных функции, которые просматривают каждый список и вызывают зарегистрированные прослушиватели. На самом деле, это – основная цель promise-объекта: управлять списками прослушивателей и вызывать эти прослушиватели тогда, когда будет нужно.

Код, который запускает асинхронную операцию, таким образом, будет использовать promise-объект для управления этими прослушивателями, отсюда и вызов new WinJS.Promise. Но так же нужно получить доступ к этим функциям в promise-объекте, которые следует вызвать для уведомления их прослушивателей. Это – цель анонимной функции, предоставленной конструктору promise-объекта. Когда promise-объект инициализирован, эти функции вызываются со ссылками на функции, которые оповещают прослушиватели. Код асинхронной операции затем сохраняет все, что нужно, для более позднего использования. В нашем случае с рабочим процессом, мы заинтересованы лишь в оповещении обработчика завершения, поэтому мы сохраняем ссылку на подходящую функцию в переменной workerCompleteDispatcher.

Когда затем мы обнаруживаем, что операция завершена – когда мы получаем соответствующее сообщение от рабочего процесса – мы проверяем, подходящая ли ссылка записана в workerCompleteDispatcher и затем вызываем ее с результирующим значением. Диспетчер, снова, перебирает все зарегистрированные прослушиватели и вызывает их с тем же результатом. В вышеприведенном коде единственный подобный прослушиватель – это анонимная функция, которую мы передали в promiseJS.done.

По правде говоря, все это чисто технические детали. Для обработки ошибок и отображения прогресса операции , мы просто точно так же сохраняем эти диспетчеры, добавляем более специфический код внутрь обработчика события onmessage , чтобы проверить e.data на другие значения статуса от рабочего процесса и, в ответ, вызываем подходящий диспетчер. Подобные взаимоотношения показаны на рис. 14.1.


Асинхронная операция (Async Operation)

Promise-объект (Promise)

Инициализация (initialize)

Регистрация прослушивателей (register listeners)

Приложение (Клиент) (App (Client))

Promise-объект управляет прослушивателями и вызывает их от имени асинхронной операции

Рис. 14.1. Promise-объект управляет прослушивателями и вызывает их от имени асинхронной операции

Снова, все, что вы видите здесь, за исключением вызова done (который является клиентским кодом, а не частью асинхронной операции), это то, что делает уровень проекции JavaScript для асинхронных операций, поступающих из WinRT. В этих случаях асинхронная операция представлена объектом с интерфейсом IAsync*, а не рабочим процессом. Вместо прослушивания события message рабочего процесса, уровень проекции просто подключает себя через интерфейс IAsync* и создает promise-объект для управления соединениями из приложения.

Вышеприведенный код включен в упражнение "Image Manipulation" в дополнительных материалах к лекции. Полезно в учебных целях установить точки останова на все анонимные функции и пошагово исполнить код для того, чтобы точно увидеть, где они вызываются, даже для того, чтобы пройти в WinJS для того, чтобы увидеть, как все это работает. В конце концов, очень важно то, что этот код предоставляет нам promise-объект (в promiseJS), который выглядит и работает точно так же как любой другой promise-объект. Это становится очень удобным, когда у нас есть promise-объекты от других асинхронных операций, как описано позже, во врезке "Объединение promise-объектов". Это означает, что мы можем смешивать и сочетать асинхронные операции из API WinRT, из компонентов WinRT и рабочих процессов.

< Лекция 13 || Лекция 14: 12345 || Лекция 15 >