Очередь асинхронных операций
Привет. Сегодня речь пойдёт о том, как облегчить создание программ, использующих асинхронные операции. Я имею ввиду загрузку мультимедийного контента, асинхронные запросы к базе данных и т.д.
Цель
Техническое задание
Реализовать возможность загрузки и хранения изображений в одном классе. Этот класс должен быть реализован на основе шаблона проектирования Singleton. Реализовать возможность последовательной загрузки нескольких изображений. Реализовать возможность выполнения методов как после загрузки определённого ресурса, так и после всех заданных загрузок.
Решение
Поговорим немного об асинхронных операциях. Flash не поддерживает многопоточность, но зато у него реализована возможность асинхронного выполнения кода. Что это значит? Если нам нужно загрузить какой-либо ресурс, например изображение или текстовый файл, то после запуска метода типа load() программа не останавливается, чтобы дождаться момента, когда ресурс будет полностью загружен. Тоже самое происходит с запросами к серверу, если мы разрабатываем браузерное приложение. После отправления запроса к серверу, программа продолжает работать и выполнять дальнейшие инструкции. О том, что сервер прислал ответ на запрос программа узнаёт после возникновения определённого события. С одной стороны это большой плюс, т.к. для пользователя подобные операции будут протекать незаметно. Но с другой стороны это может вызвать трудности при разработке.
В этом уроке я расскажу как сделать простейший менеджер ресурсов, который будет загружать изображения и хранить их на всём жизненном цикле программы. Для последовательной загрузки будем использовать очередь загрузок. Т.е. загрузка очередного файла происходит после загрузки предыдущего. Все загруженные данные будут храниться в объекте типа Dictionary
. Данный класс по структуре напоминает класс HashMap
из языка Java
, т.е. в качестве ключей словаря могут использоваться не только строки, но и любые другие объекты.
Запускаем Flash Builder и создаём проект Action Script. Назовём его QueueTest. Сразу начнём с создания класса SourceManager
. Определим, что нам нужно. Во первых, для реализации шаблона Singleton нужна переменная instance. Во-вторых, нужен диспетчер событий для информирования о завершении очереди загрузок и статичная константа для типа события. Также нужен массив имён загружаемых файлов, хранилище данных, хранилище хэндлеров и несколько ссылок для выполнения очередной загрузки. Вот как выглядит наш класс в первоначальном виде:
package { import flash.display.*; import flash.events.*; import flash.events.EventDispatcher; import flash.utils.Dictionary; public class SourceManager { /** Тип события, возникающего после загрузки всех файлов в очереди */ public static const LOADING_COMPLETE:String = 'loading_complete'; protected static var _instance:SourceManager; //тут храним объект данного класса protected var _dispatcher:EventDispatcher; //диспетчер для создания события protected var _queue:Vector.<String>; //вектор имён файлов protected var _handlers:Dictionary; //словарь функций для вызова после загрузки определённого файла protected var _busy:Boolean; //флаг выполнения очереди protected var _currentFileName:String; //имя очередного файла protected var _currentLoader:Loader; //загрузчик очередного файла protected var _dataStore:Dictionary; //хранилище данных public function SourceManager(singleton:Singleton) { _dispatcher = new EventDispatcher(); _queue = new Vector.; _handlers = new Dictionary(); _busy = false; _dataStore = new Dictionary(); } public static function get instance():SourceManager { if (!_instance) _instance = new SourceManager(new Singleton()); return _instance; } } } class Singleton { //Реализация шаблона Singleton }
С помощью внутреннего класса Singleton
мы исключаем возможность создания объекта SourceManager
в любом месте приложения.
На самом деле в нашем примере можно использовать обычный массив вместо класса Vector
, а класс Dictionary
заменить на Object
. Но задача урока рассмотреть именно данные классы, поэтому я буду использовать их.
Теперь реализуем очередь загрузки файлов. При вызове метода загрузки мы не создаём загрузчика, а всего лишь добавляем имя файла в вектор имён, из которого последовательно будем эти имена доставать. Для загрузки изображений используется класс Loader
. Это необходимо учесть, т.к. загрузку текстовых файлов, файлов XML необходимо реализовывать с помощью класса URLLoader
. Ниже я приведу полный листинг метода загрузки и поясню некоторые моменты в комментариях:
public static function loadSource(fileName:String, handler:Function = null):void { //через статичный метод класса вызываем метод экземпляра _instance.loadFile(fileName, handler); } protected function loadFile(fileName:String, handler:Function):void { //добавляем имя файла в очередь _queue.push(fileName); //если указан метод для выполнения после загрузки данного файла, запоминаем его if (handler != null) _handlers[fileName] = handler; //если ничего не загружается, то запускаем очередь if (!_busy) { loadNextFile(); _busy = true; } } protected function loadNextFile():void { //запоминаем текущее имя файла и создаём текущий загрузчик _currentFileName = _queue.shift(); _currentLoader = new Loader(); //вешаем слушателей загрузчику addHandlers(); //загружаем _currentLoader.load(new URLRequest(_currentFileName)); } protected function addHandlers():void { _currentLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler); _currentLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); } protected function completeHandler(event:Event):void { //удаляем слушателей загрузчика removeHandlers(); //сохраняем загруженные данные _dataStore[_currentFileName] = _currentLoader.content; //если у текущего файла был хэндлер, то выполняем его var handler:Function = _handlers[_currentFileName] as Function; if (handler != null) { handler(_currentLoader.content); //...и удаляем после выполнения _handlers[_currentFileName] = null; delete _handlers[_currentFileName]; } //проверяем очередь загрузок checkQueue(); } protected function ioErrorHandler(event:IOErrorEvent):void { removeHandlers(); trace(event.text); checkQueue(); } protected function removeHandlers():void { _currentLoader.removeEventListener(Event.COMPLETE, completeHandler); _currentLoader.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); } protected function checkQueue():void { //если в очереди есть файлы, то загружаем очередной if (_queue.length > 0) loadNextFile(); //либо сбрасываем флаг и создаём событие else { _busy = false; _dispatcher.dispatchEvent(new Event(LOADING_COMPLETE)); } }
Теперь чтобы получить загруженный файл по имени, нужно обратиться к хранилищу данных по ключу, в качестве которого это имя используется. Главный класс проекта будет выглядеть следующим образом
public static function getSource(fileName:String):DisplayObject { //через статичный метод класса вызываем метод экземпляра return _instance.getFile(fileName); } protected function getFile(fileName:String):DisplayObject { //получаем данные из хранилища var data:DisplayObject = _dataStore[fileName]; //если данные существуют, то возвращаем if (data) return data; //если нет, то возвращаем null trace('Файл "' + fileName + '" не загружен'); return null; }
Теперь осталось протестировать наш класс. Для этого в папке с проектом создадим папку assets и положим туда 4 изображения. Их мы будем загружать. Для 2-го изображения создадим хэндлер, который выполниться сразу после загрузки.
package { import flash.display.*; import flash.events.Event; public class QueueTest extends Sprite { public function QueueTest() { //стандартно для проекта Action Script if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } protected function init(event:Event = null):void { //изменяем свойства приложения removeEventListener(Event.ADDED_TO_STAGE, init); stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; //сразу вешаем слушатель на загрузку всех файлов SourceManager.instance.dispatcher.addEventListener(SourceManager.LOADING_COMPLETE, allFilesLoaded); //...и загружаем SourceManager.loadSource('../assets/pict01.jpg'); SourceManager.loadSource('../assets/pict02.jpg', file2Loaded); //указываем хэндлер SourceManager.loadSource('../assets/pict03.jpg'); SourceManager.loadSource('../assets/pict04.jpg'); } protected function file2Loaded(data:DisplayObject):void { trace('Файл 2 загружен'); stage.addChild(data); } protected function allFilesLoaded(event:Event):void { SourceManager.instance.dispatcher.removeEventListener(SourceManager.LOADING_COMPLETE, allFilesLoaded); trace('Все файлы загружены') testSource(); } protected function testSource():void { var pict:DisplayObject; //показываем загруженный файл pict = SourceManager.getSource('../assets/pict04.jpg'); if (pict) { pict.x = 200; stage.addChild(pict); } //пытаемся получить не загруженный файл pict = SourceManager.getSource('../assets/pict05.jpg'); if (pict) { pict.x = 400; stage.addChild(pict); } } } }
Готово. Запускаем проект и видим 2 изображения и 3 сообщения в отладчике. С ними, я думаю, вы разберётесь сами.
Данный проект — всего лишь пример использования очереди. Эту технику можно использовать и при получении данных с сервера, либо при создании диалогов в играх и .т.д. Фантазия подскажет. Удачных проектов…
Результат
Тэги: action script 3.0, программирование, средние
Ссылка для вашего сайта.
Комментарии (2)
Роман
| #
Интересная статья. У меня только возник вопрос по реализации Singleton.
Когда то вычитал вот такой способ:
Вот у меня и возник вопрос, а не будет ли вышеизложенный вариант более корректной реализацией для данного шаблона? Если нет, то почему?
Ответить
Андрей
| #
Здравствуйте, Роман. Хороший вопрос. На самом деле я взял за основу пример реализации Singleton из книги «Action Script 3.0 Шаблоны проектирования». Но, действительно, сейчас обнаружил небольшую недоработку. О ней чуть ниже.
Что касается Вашего вопроса. Лично мне вариант с внутренним классом нравится больше, т.к. при использовании конструктора класса
SourceManager()
возникнет ошибка на этапе компиляции. Конструктор требует параметрsingleton:Singleton
. А это внутренний класс, экземпляр которого можно создать только в классеSourceManager
В Вашем случае ошибка возникнет только на этапе выполнения. Это может создать дополнительные неудобства. Лучше увидеть ошибку сразу, чем когда приложение откомпилировано и загружено на сервер.Теперь о доработке моего примера. Очевидно, ошибку компиляции можно обойти, передав в качестве параметра
null
. Для того, чтобы нельзя было таким образом создать экземпляр классаSourceManager
, нужно добавить проверку наnull
в конструктор. К сожалению, эта ошибка возникнет только на этапе выполнения.Ответить