40
Асинхронное программирование Методы реализации основных асинхронных задач в различных платформах и микрофреймворк для ActionScript

FPUG Dzyga presentation

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: FPUG Dzyga presentation

АсинхронноепрограммированиеМетоды реализации основныхасинхронных задач в различныхплатформахи микрофреймворк для ActionScript

Page 2: FPUG Dzyga presentation

Часть I- Основные задачи асинхронного программирования- ActionScript: Event, EventDispatcher, ENTER_FRAME- JavaScript: onclick etc., EventDispatcher- jQuery: on, trigger, $.Deferred, $.Callbacks, $.when- Python: tornadoweb, IOLoop

Page 3: FPUG Dzyga presentation

Основные задачи- Создание интерактива для GUI- Клиент-серверное взаимодействие- Загрузка ассетов- Анимация- Взаимодействие между различными частями приложения припомощи жестких однонаправленных связей (REQ-REP).- Взаимодействие между различными частями приложения припомощи слабых связей (PUB-SUB).- Фоновые процессы

Page 4: FPUG Dzyga presentation

ActionScript 3

Page 5: FPUG Dzyga presentation

Event, EventDispatcher- Отлично подходит для реализации интерактива в GUI.Собственно, для этого и создавался.- Клиент-серверные приложения, загрузка ассетов - не оченьудобно, нативный Loader мало кто использует. Чаще всегоBulkLoader, или велосипеды.

- Анимация, фоновые процессы - самый распространненныйподход - через ENTER_FRAME. Не очень хорошо подходит ни длятого, ни для другого. Для фоновых процессов можноиспользовать LocalConnection, который работает на тех жеивентах, или Worker. С анимацией все намного печальнее.- Реализация однонаправленных жестких связей - очень плохо.Нельзя явно описать в интерфейсе или классе-прототипе.Оверхед.- Слабые связи при помощи Event - нарушение инкапсуляции изкоробки.

Page 6: FPUG Dzyga presentation

Проблемы реализации.- В нативном AS3 Event используется везде, хотя проектировалсяглавным образом для GUI. Всвязи с чем содержит многоизбыточного функционала (target, currentTarget, bubbling,capture).

- Проблемы с клонированием в кастомных ивентах GUI. Бажнаяреализация самого процесса клонирования.- Жесткое требование субкласса дляEventDispatcher.dispatchEvent()

- Использование "кнопочного" программирования флешерамидля любой задачи.

Page 7: FPUG Dzyga presentation

JavaScript, jQuery, Backbone

Page 8: FPUG Dzyga presentation

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';

}

Page 9: FPUG Dzyga presentation

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...

Page 10: FPUG Dzyga presentation

Backbone.Events- Чистая, минималистическая реализация, без использованиянативных JS ивентов.- Реализован как миксин. То есть может расширять любой объектили его прототип.- Метод on позволяет подписаться на "иерархические" ивенты, атакже на несколько ивентов сразу.- listenTo и stopListening позволяют держать все хендлеры втекущем объекте, в том числе хендлеры для внешних объектови потом быстро их чистить.- trigger может посылать несколько ивентов сразу. Как и в jQueryесть возможность передавать аргументы в хендлер.

Page 11: FPUG Dzyga presentation

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/

Page 12: FPUG Dzyga presentation

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/

Page 13: FPUG Dzyga presentation

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

Page 14: FPUG Dzyga presentation

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/

Page 15: FPUG Dzyga presentation

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.

Page 16: FPUG Dzyga presentation

Python, tornadoweb

Page 17: FPUG Dzyga presentation

tornado.concurrent.FutureИнкапсулирует результат асинхронной операции. Ближайшийаналог - $.Callbacks. Отличие - $.Callbacks не имеет никакойсвязи с результатом. Привязка ни к объекту, а к процессу и егорезультату. Типичный подход для асинхронногопрограммирования на сервере.

Описание интерфейса- result() - возвращает результат операции, или выбрасываетексепшен.- exception() - возвращает исключение, как объект.- add_done_callback(fn) - регистрирует колбек, которыйвыполнится когда процесс будет завершен. С экземпляромFuture в качестве аргумента.

- done() - возвращает True, если процес завершен.

Page 18: FPUG Dzyga presentation

tornado.ioloopАсинхронные процессы на сервере как правило связанны ссокетами. Сокет это объект, похожий на файл, который можетнаходится в двух состояниях: запись и чтение. I/O Loopиспользует функцию ядра OS (epoll в Linux) для того, чтобыопределить когда сокет изменит свое состояние.

Page 19: FPUG Dzyga presentation

Описание интерфейса(не полное)

- 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.

Page 20: FPUG Dzyga presentation

Часть II- Dzyga- DispatcherProxy - работа с ивентами- Promise, Once - работа с колбеками- Task - абстракция для асинхронных процессов- Loop, LoopTask - менеджер колбеков для ENTER_FRAME, реализацияфоновых процессов, инкапсуляция интерактива для GUI.

Page 21: FPUG Dzyga presentation

org.dzyga.events

Page 22: FPUG Dzyga presentation

org.dzyga.event.DispatcherProxy- Расширение функционала EventDispatcher.- Облегчение работы с нативными флешовыми EventDispatcher.- Привязка добавленных к флешовому EventDispatcherобработчиков событий к конкретному классу с возможностьюбыстрого удаления зарегистрированных обработчиков.- Улучшение производительности, за счет группировки большогочисла обработчиков в очередь. Но в большинстве случаев этобудет работать, конечно, медленнее.

Page 23: FPUG Dzyga presentation

Краткое описание:Дублирует функционал для добавления обработчиков событий атакже проксирует нативные методы. Реализует IEventDispatcher.Для обработчиков добавляемых через DispatcherProxy создаютсяэкземпляры класса EventListener, которые затем сохраняются влокальную для данного экземпляра коллекцию для обеспечениялучших возможностей манипуляции обработчиком, чем внативном IEventDispatcher.Позволяет добавлять аргументы в обработчики событий, биндитьих к другим объектам, а также регистрировать обработчики,которые автоматически удаляются после первого вызова.

Page 24: FPUG Dzyga presentation

Описание интерфейсаДобавление обработчика к проксируемому диспетчеру: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;

Page 25: FPUG Dzyga presentation

Удаление обработчиков, зарегистрированных данным прокси содного или всех диспетчеров: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;

Page 26: FPUG Dzyga presentation

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);

Пример кода с синтаксическимсахаром

Page 27: FPUG Dzyga presentation

org.dzyga.callbacks

Page 28: FPUG Dzyga presentation

org.dzyga.callbacks.Promise- Замена и расширение функционала EventDispatcher.- Более явная реализация обработки асинхронных процессов -promise можно объявлять свойством класса и/или интерфейса.- Реализация жестких однонаправленных связей между частямиприложения: модуль добавляющий колбек в промис имеетдоступ к модулю обработчику, модуль обработчик в обратнуюсторону - нет.

Краткое описаниеДля добавляемых колбеков создаются инстансы класса Callback,которые помещаются в сортированный список. При вызовеметода resolve все находящиеся в списке колбеки выполняются.Аргументы, заданные при добавлении колбека добавляются вконец списка аргументов, с которыми была вызван метод resolve.

Page 29: FPUG Dzyga presentation

Описание интерфейсаДобавление колбека:function callbackRegister (

callback:Function, once:Boolean = false, thisArg:* = null,argsArray:Array = null):IPromise;

Удаление колбека:function callbackRemove (callback:Function = null):IPromise;

Запуск колбеков (выполнение обещания):function resolve (... args):IPromise;

Page 30: FPUG Dzyga presentation

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();}

Page 31: FPUG Dzyga presentation

org.dzyga.callbacks.OnceПростейший субкласс Promise. Если Once был хоть один раззарезолвлен, то все колбеки, добавляемые в будующем,запускаются сразу же после добавления. Как и Promise может бытьзарезолвлен сколько угодно раз. Предусмотрена возможностьповторного использования, при помощи метода reset().

Page 32: FPUG Dzyga presentation

org.dzyga.callbacks.Task- Реализация асинхронных процессов- Инкапсуляция переменных и методов связанных с процессом.- Отделение функционала процесса от объектов, учавствующих впроцессе. В теории, может вести к большим возможностямповторного использования кода.- Организация жестких однонаправленных связей междумодулями.

Page 33: FPUG Dzyga presentation

Краткое описаниеCодержит промисы, которые резолвятся на разных стадияхпроцесса, и куда, внешние модули могут добавлять колбеки:- started - резолвится при запуске асинхронного процесса.- done - резолвится при успешном завершении процесса.- failed - резолвится при неправильном завершении процесса.- finished - резолвится при любом завершении процесса, сразупосле done или failed.- progress - резолвится на промежуточных стадиях процесса.Кроме промисов содержит еще поле state, т.е. Task являетсястейт-машиной.

Page 34: FPUG Dzyga presentation

Описание интерфейсаЗапуск процесса, резолв промиса 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;

Page 35: FPUG Dzyga presentation

org.dzyga.eventloop

Page 36: FPUG Dzyga presentation

org.dzyga.eventloop.Loop- Реализация фоновых процессов.- Анимация.- Обработка интерактива (замена MOUSE_MOVE)- Ограничение количества запускаемых каждый фреймобработчиков, с целью поддерживать заданный fps.

Краткое описание:Позволяет запускать колбеки каждый фрейм, в том же фрейме, нов другом стеке, если есть возможность, или по таймауту.Поддерживает приоритеты. Если очередь колбеков выполняетсядольше зависящего от fps времени - выполнение колбеков в этомфрейме прекращается, что позволяет поддерживать нужный фпс,жертвуя, например временем обработки фоновых процессов.

Page 37: FPUG Dzyga presentation

Описание интерфейсаДобавление колбека, который должен запускаться каждый фрейм: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;

Page 38: FPUG Dzyga presentation

org.dzyga.eventlooop.LoopTaskСлужит для реализации задач, требующих значительноговремени для выполнения: например - препроцессинг анимации.Метод run экземпляра класса будет выполнятся при помощиметода Loop.call каждый фрейм, столько раз, сколько позволяетвремя. Можно задавать приоритет выполнения колбека. Этосубкласс Task.org.dzyga.eventlooop.FrameEnterTaskСлужит для реализации процессов, которые отвечают заобновление экрана (анимация) или за отслеживаниепроисходящего на экране (мышь, положение движущихсяобъектов). Также как LoopTask должен содержать метод run,который при помощи метода Loop.frameEnterCall будетзапускаться каждый фрейм.

Page 39: FPUG Dzyga presentation

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(оптимизирован для просмотра)

Page 40: FPUG Dzyga presentation

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;}

Пример реализации интерактива