Upload
ivan-filimonov
View
668
Download
1
Embed Size (px)
DESCRIPTION
Citation preview
АсинхронноепрограммированиеМетоды реализации основныхасинхронных задач в различныхплатформахи микрофреймворк для ActionScript
Часть I- Основные задачи асинхронного программирования- ActionScript: Event, EventDispatcher, ENTER_FRAME- JavaScript: onclick etc., EventDispatcher- jQuery: on, trigger, $.Deferred, $.Callbacks, $.when- Python: tornadoweb, IOLoop
Основные задачи- Создание интерактива для GUI- Клиент-серверное взаимодействие- Загрузка ассетов- Анимация- Взаимодействие между различными частями приложения припомощи жестких однонаправленных связей (REQ-REP).- Взаимодействие между различными частями приложения припомощи слабых связей (PUB-SUB).- Фоновые процессы
ActionScript 3
Event, EventDispatcher- Отлично подходит для реализации интерактива в GUI.Собственно, для этого и создавался.- Клиент-серверные приложения, загрузка ассетов - не оченьудобно, нативный Loader мало кто использует. Чаще всегоBulkLoader, или велосипеды.
- Анимация, фоновые процессы - самый распространненныйподход - через ENTER_FRAME. Не очень хорошо подходит ни длятого, ни для другого. Для фоновых процессов можноиспользовать LocalConnection, который работает на тех жеивентах, или Worker. С анимацией все намного печальнее.- Реализация однонаправленных жестких связей - очень плохо.Нельзя явно описать в интерфейсе или классе-прототипе.Оверхед.- Слабые связи при помощи Event - нарушение инкапсуляции изкоробки.
Проблемы реализации.- В нативном AS3 Event используется везде, хотя проектировалсяглавным образом для GUI. Всвязи с чем содержит многоизбыточного функционала (target, currentTarget, bubbling,capture).
- Проблемы с клонированием в кастомных ивентах GUI. Бажнаяреализация самого процесса клонирования.- Жесткое требование субкласса дляEventDispatcher.dispatchEvent()
- Использование "кнопочного" программирования флешерамидля любой задачи.
JavaScript, jQuery, Backbone
JavaScript. Event.- Работа с ивентами реализованна практически также, как вActionScript. Только есть пережитки прошлого.- Используется программистами в основном для интерактива идля загрузки контента. Или я просто не знаю, поэтому сплюспокойно.- Нет ENTER_FRAME - для анимации и подобных задач чаще всегоиспользуется setInterval. И это более правильно.- В javascript гораздо более простая и удобная работа сфункциями: binding, нет глюков с анонимками.
elementOne.addEventListener('click', doSomething, false);elementTwo.addEventListener('click', doSomething, false);elementDyno.onclick = doSomething;
function doSomething () {this.style.backgroundColor = '#cc0000';
}
jQuery.Event- Расширение нативного функционала.- Методы on, one - намного более удобный способрегистрировать хендлеры, чем addEventListener.- Метод trigger позволяет передавать в хендлер дополнительныеаргументы.- Любой инстанс jQuery имеет в своем прототипе этотфункционал. Мне это не нравится.
function greet (event, silent) {if (!silent) {
alert("Hello " + event.data.name);}
}
$("button").on("click", {name: "Karl"}, greet);$("button").on("click", ".addy", {name: "Addy"}, greet);$("button").trigger("click", true); // nothing happens...
Backbone.Events- Чистая, минималистическая реализация, без использованиянативных JS ивентов.- Реализован как миксин. То есть может расширять любой объектили его прототип.- Метод on позволяет подписаться на "иерархические" ивенты, атакже на несколько ивентов сразу.- listenTo и stopListening позволяют держать все хендлеры втекущем объекте, в том числе хендлеры для внешних объектови потом быстро их чистить.- trigger может посылать несколько ивентов сразу. Как и в jQueryесть возможность передавать аргументы в хендлер.
var emitter = {name: 'emitter',handler: function (msg) {
console.log(this.name + ' received: ' + msg);}
};_.extend(emitter, Backbone.Events);
emitter.on("alert", function (msg) {console.log("message: " + msg);
});
emitter.trigger("alert", "an event");emitter.trigger("alert:foo", "namespaced event");
var external = _.extend({}, Backbone.Events);emitter.listenTo(external, 'changed removed', emitter.handler);external.trigger('changed', 'an event');emitter.stopListening(external, 'changed');external.trigger('changed', 'an event');external.trigger('removed', 'bye!');
http://jsfiddle.net/457Yh/
jQuery.CallbacksХороший пример реализации однонаправленной жесткой связимежду частями приложения.
var foo = function (value) {console.log("foo:" + value);
};
var callbacks = $.Callbacks();callbacks.add(foo);callbacks.fire("hello");callbacks.fire("world");
var bar = function( value ){console.log( "bar:" + value );
};callbacks.add(bar);callbacks.fire("hello again");
http://jsfiddle.net/DZNHU/
jQuery.Deferred, jQuery.when.Класс для реализации однонаправленных жестких связей междуприложением и асинхронным процессом. Описывает основныесостояния процесса, и дает возможность приложениюрегистрировать колбеки для каждого из состояний. Позволяетсоздавать сложные процессы, за счет чейнинга. В jQueryпредлагается использовать для реализации клиент-серверныхвзаимодействий - все ajax запросы возвращают Deferred. Можноиспользовать для реализации любых "процессов".
var deferred = $.Deferred();
function foo (msg) {console.log('foo: ' + msg);
}function bar (msg) {
console.log('bar: ' + msg);}
deferred.done([foo, bar], foo).fail(foo);
$('button[name="resolve"]').click(function () {deferred.resolve('Ok...');return false;
});
$('button[name="reject"]').click(function () {deferred.reject('Not ok...');return false;
});
http://jsfiddle.net/sWvY6/
var checkRequest = function (a1, a2) {var data = a1[0] + a2[0];if (/Whip It/.test(data)) {
alert( "We got what we came for!" );}
}
$.when(ajax("/page1.php"), ajax("/page2.php")).done(function (a1, a2) {
// a1 and a2 are arguments resolved for// the page1 and page2 ajax requests, respectively.// Each argument is an array// with the following structure: [data, statusText, jqXHR]// a1[0] = "Whip", a2[0] = " It"checkRequest(a1, a2);
}); // Alerts after both pages loaded.
// Nasty hack$.when(ajax("/page1.php"), [" It"]])
.done(checkRequest); // Alerts when page1 is loaded.
Python, tornadoweb
tornado.concurrent.FutureИнкапсулирует результат асинхронной операции. Ближайшийаналог - $.Callbacks. Отличие - $.Callbacks не имеет никакойсвязи с результатом. Привязка ни к объекту, а к процессу и егорезультату. Типичный подход для асинхронногопрограммирования на сервере.
Описание интерфейса- result() - возвращает результат операции, или выбрасываетексепшен.- exception() - возвращает исключение, как объект.- add_done_callback(fn) - регистрирует колбек, которыйвыполнится когда процесс будет завершен. С экземпляромFuture в качестве аргумента.
- done() - возвращает True, если процес завершен.
tornado.ioloopАсинхронные процессы на сервере как правило связанны ссокетами. Сокет это объект, похожий на файл, который можетнаходится в двух состояниях: запись и чтение. I/O Loopиспользует функцию ядра OS (epoll в Linux) для того, чтобыопределить когда сокет изменит свое состояние.
Описание интерфейса(не полное)
- IOLoop.add_handler(fd, handler, events) - регистрирует колбекhandler, который будет выполнен, когда сокет с дескриптором fd"сгенерирует" один из ивентов в списке events.- IOLoop.add_callback(callback, *args, **kwargs) - запускаетколбек на следующей итерации IOLoop.- IOLoop.add_future(future, callback) - запускает колбек наследующей итерации IOLoop, после того, как future будетвыполнено.- IOLoop.add_timeout(deadline, callback) - запускает кобек посленаступления deadline.
Часть II- Dzyga- DispatcherProxy - работа с ивентами- Promise, Once - работа с колбеками- Task - абстракция для асинхронных процессов- Loop, LoopTask - менеджер колбеков для ENTER_FRAME, реализацияфоновых процессов, инкапсуляция интерактива для GUI.
org.dzyga.events
org.dzyga.event.DispatcherProxy- Расширение функционала EventDispatcher.- Облегчение работы с нативными флешовыми EventDispatcher.- Привязка добавленных к флешовому EventDispatcherобработчиков событий к конкретному классу с возможностьюбыстрого удаления зарегистрированных обработчиков.- Улучшение производительности, за счет группировки большогочисла обработчиков в очередь. Но в большинстве случаев этобудет работать, конечно, медленнее.
Краткое описание:Дублирует функционал для добавления обработчиков событий атакже проксирует нативные методы. Реализует IEventDispatcher.Для обработчиков добавляемых через DispatcherProxy создаютсяэкземпляры класса EventListener, которые затем сохраняются влокальную для данного экземпляра коллекцию для обеспечениялучших возможностей манипуляции обработчиком, чем внативном IEventDispatcher.Позволяет добавлять аргументы в обработчики событий, биндитьих к другим объектам, а также регистрировать обработчики,которые автоматически удаляются после первого вызова.
Описание интерфейсаДобавление обработчика к проксируемому диспетчеру:function listen (
eventType:String, callback:Function, once:Boolean = false,thisArg:* = null, argArray:Array = null):IDispatcherProxy;
Удаление обработчиков с проксируемого диспетчера:function stopListening (
eventType:String = '',callback:Function = null):IDispatcherProxy;
Добавление обработчика к любому диспетчеру:function listenTo (
target:IEventDispatcher, eventType:String,callback:Function, once:Boolean = false,thisArg:* = null, argArray:Array = null):IDispatcherProxy;
Удаление обработчиков, зарегистрированных данным прокси содного или всех диспетчеров:function stopListeningTo (
target:IEventDispatcher = null, eventType:String = '',callback:Function = null):IDispatcherProxy;
Триггер для простых ивентов:function triggerTo (
target:IEventDispatcher, eventType:String):IDispatcherProxy;
Проверка зарегистрированных обработчиков:function isListeningTo (
target:IEventDispatcher = null, eventType:String = '',callback:Function = null):Boolean;
dispatcher(d).listen(MouseEvent.CLICK, onClick).listen(MouseEvent.CLICK, onAnotherClick).listen(MouseEvent.ROLL_OVER, onOver).listen(MouseEvent.ROLL_OUT, onOut).listen(MouseEvent.MOUSE_OUT, onOut)// Next callback will be removed after the first run..listen(
Event.ADDED_TO_STAGE, initView,true, null, ['target init']);
dispatcher(d).trigger(MouseEvent.CLICK).trigger(MouseEvent.ROLL_OVER).trigger(Event.ADDED_TO_STAGE)// Nothing happens there....trigger(Event.ADDED_TO_STAGE);
Пример кода с синтаксическимсахаром
org.dzyga.callbacks
org.dzyga.callbacks.Promise- Замена и расширение функционала EventDispatcher.- Более явная реализация обработки асинхронных процессов -promise можно объявлять свойством класса и/или интерфейса.- Реализация жестких однонаправленных связей между частямиприложения: модуль добавляющий колбек в промис имеетдоступ к модулю обработчику, модуль обработчик в обратнуюсторону - нет.
Краткое описаниеДля добавляемых колбеков создаются инстансы класса Callback,которые помещаются в сортированный список. При вызовеметода resolve все находящиеся в списке колбеки выполняются.Аргументы, заданные при добавлении колбека добавляются вконец списка аргументов, с которыми была вызван метод resolve.
Описание интерфейсаДобавление колбека:function callbackRegister (
callback:Function, once:Boolean = false, thisArg:* = null,argsArray:Array = null):IPromise;
Удаление колбека:function callbackRemove (callback:Function = null):IPromise;
Запуск колбеков (выполнение обещания):function resolve (... args):IPromise;
private function firstCallback (inc:int = 1):void {_firstCounter += inc;
}
private function secondCallback (inc:int = 1):void {_secondCounter += inc;
}
[Test]public function testCallbackRegisterUniq ():void {
var promise:Promise = new Promise();promise
.callbackRegister(firstCallback, false, null, [2])
.callbackRegister(firstCallback)
.callbackRegister(secondCallback)
.resolve();// 3, if first callback added twice...assertEquals(2, _firstCounter);assertEquals(1, _secondCounter);
promise.clear();}
org.dzyga.callbacks.OnceПростейший субкласс Promise. Если Once был хоть один раззарезолвлен, то все колбеки, добавляемые в будующем,запускаются сразу же после добавления. Как и Promise может бытьзарезолвлен сколько угодно раз. Предусмотрена возможностьповторного использования, при помощи метода reset().
org.dzyga.callbacks.Task- Реализация асинхронных процессов- Инкапсуляция переменных и методов связанных с процессом.- Отделение функционала процесса от объектов, учавствующих впроцессе. В теории, может вести к большим возможностямповторного использования кода.- Организация жестких однонаправленных связей междумодулями.
Краткое описаниеCодержит промисы, которые резолвятся на разных стадияхпроцесса, и куда, внешние модули могут добавлять колбеки:- started - резолвится при запуске асинхронного процесса.- done - резолвится при успешном завершении процесса.- failed - резолвится при неправильном завершении процесса.- finished - резолвится при любом завершении процесса, сразупосле done или failed.- progress - резолвится на промежуточных стадиях процесса.Кроме промисов содержит еще поле state, т.е. Task являетсястейт-машиной.
Описание интерфейсаЗапуск процесса, резолв промиса started с переданнымиаргументами:function start (... args):ITask;
Переключение состояний процесса и резолв соответствующихпромисов:function notify (... args):ITask;function resolve (... args):ITask;function reject (... args):ITask;
Очистка всех промисов и сброс состояния процесса:function clear ():ITask;
Возвращает одно из значений: TaskState.STARTED,TaskState.RESOLVED, TaskState.REJECTED или TaskState.IDLE:function get state ():String;
org.dzyga.eventloop
org.dzyga.eventloop.Loop- Реализация фоновых процессов.- Анимация.- Обработка интерактива (замена MOUSE_MOVE)- Ограничение количества запускаемых каждый фреймобработчиков, с целью поддерживать заданный fps.
Краткое описание:Позволяет запускать колбеки каждый фрейм, в том же фрейме, нов другом стеке, если есть возможность, или по таймауту.Поддерживает приоритеты. Если очередь колбеков выполняетсядольше зависящего от fps времени - выполнение колбеков в этомфрейме прекращается, что позволяет поддерживать нужный фпс,жертвуя, например временем обработки фоновых процессов.
Описание интерфейсаДобавление колбека, который должен запускаться каждый фрейм:function frameEnterCall (
callback:Function, priority:uint = 1,thisArg:* = null, argsArray:Array = null):ILoopCallback;
Добавление колбека, который должен запуститься в ближайшеевозможное время:function call (
callback:Function, priority:uint = 1,thisArg:* = null, argsArray:Array = null):ILoopCallback;
Добавление колбека по таймауту:function delayedCall (
callback:Function, delay:Number = 0, priority:uint = 1,thisArg:* = null, argsArray:Array = null):ILoopCallback;
org.dzyga.eventlooop.LoopTaskСлужит для реализации задач, требующих значительноговремени для выполнения: например - препроцессинг анимации.Метод run экземпляра класса будет выполнятся при помощиметода Loop.call каждый фрейм, столько раз, сколько позволяетвремя. Можно задавать приоритет выполнения колбека. Этосубкласс Task.org.dzyga.eventlooop.FrameEnterTaskСлужит для реализации процессов, которые отвечают заобновление экрана (анимация) или за отслеживаниепроисходящего на экране (мышь, положение движущихсяобъектов). Также как LoopTask должен содержать метод run,который при помощи метода Loop.frameEnterCall будетзапускаться каждый фрейм.
public function get callback ():Function {if (hasOwnProperty('run')) {
return this['run'];} else {
return FunctionUtils.identity;}
}
protected function loopCallbackRegister ():void {if (!_loopCallback) {
_loopCallback = _loop.call(runner, priority);
}}
protected function runner ():void {if (state == TaskState.STARTED) {
_result = callback.apply(thisArg, argsArray);
}if (state == TaskState.STARTED) {
loopCallbackRegister();}
}
protected function loopCallbackCancel ():void {if (_loopCallback) {
_loopCallback.cancel();_loopCallback = null;
}}
protected function loopCallbackRemove ():void {if (_loopCallback) {
_loop.callbackRemove(_loopCallback);_loopCallback = null;
}}
override public function start (...args):ITask {loopCallbackRegister();return super.start.apply(this, args);
}
override public function resolve (...args):ITask {loopCallbackCancel();return super.resolve.apply(this, args);
}
override public function reject (...args):ITask {loopCallbackCancel();return super.reject.apply(this, args);
}
override public function clear ():ITask {loopCallbackRemove();return super.clear();
}
Исходник LoopTask(оптимизирован для просмотра)
function loopCallbackRegister ():void {if (_trackingActive) {
super.loopCallbackRegister();}
}
function trackingEnable (e:Event = null):void {_trackingActive = true;loopCallbackRegister();
}
function trackingDisable (e:Event = null):void {_trackingActive = false;loopCallbackCancel();
}
function get trackingActive ():Boolean {return _trackingActive;
}
function run ():ICell {var cell:LevelCell = cellMap.cellGet(
_view.mouseX, _view.mouseY);if (cell != _cell) {
_cell = cell;notify(_cell);
}return _cell;
}
function start (...args):ITask {_trackingActive = _view.active;_view
.listen(LevelView.LEVEL_ACTIVATE_EVENT,trackingEnable)
.listen(LevelView.LEVEL_DEACTIVATE_EVENT,trackingDisable);
return super.start.apply(this, args);}
function stop ():void {_view
.stopListening(LevelView.LEVEL_DEACTIVATE_EVENT,trackingDisable)
.stopListening(LevelView.LEVEL_ACTIVATE_EVENT,trackingEnable);
_trackingActive = false;}
Пример реализации интерактива