Программирование мобильных устройств на платформе .NET Compact Framework - Иво Салмре
Шрифт:
Интервал:
Закладка:
■ Синхронный код пишется гораздо быстрее. И это правда. Когда перед вами маячат сроки контрольного этапа, очень легко убедить себя в том, что единственный способ закончить работу вовремя — это срезать углы, организовав выполнение коммуникационной логики синхронно с выполнением логики пользовательского интерфейса.
■ Коль скоро мы не забываем о необходимости осуществления коммуникаций в асинхронном режиме и предусматриваем это в проекте приложения, то организовать впоследствии обмен данными в асинхронном режиме не составит никакого труда. Это — заблуждение. Несомненно, если вы не забываете о том, что, в конечном счете, коммуникационные операции должны будут выполняться асинхронно, то это облегчит вам переориентирование написанных вами функций синхронной связи на асинхронный режим выполнения в будущем, но одного этого еще мало. Истина состоит в том, что, как бы вы ни старались это предотвратить, в код, использующий синхронные процедуры, будут "намертво" встроены синхронные зависимости. Человек просто не в состоянии уследить за всеми неявными допущениями, которые вплетаются в логику приложения, и устранить возникающие из-за этого проблемы на более поздних этапах разработки приложения вам будет очень трудно.
Наилучшая методология проектирования коммуникационного кода, который должен обеспечивать постоянную способность пользовательского интерфейса к отклику, заключается в том, чтобы следовать приведенным ниже четырем рекомендациям:
1. Проектируйте свои коммуникационные процедуры в виде отдельных, надежно инкапсулированных функций. Лучший способ подготовить коммуникационные процедуры к асинхронному выполнению — это надежно их инкапсулировать. Коммуникационная процедура, которая пересылает данные на сервер, должна передавать статическую копию этих данных; для получения этих данных функции не должен требоваться доступ к каким-либо глобальным либо разделяемым состояниям. Аналогичным образом, коммуникационная процедура, осуществляющая считывание данных с сервера, не должна изменять никакое глобальное состояние приложения, пока не прочитает все данные. Очень важно соблюдать эти принципы, поскольку одновременная работа двух потоков с одним и тем же глобальным состоянием приложения — это верный путь к дополнительным сложностям, разрушению данных и снижению надежности приложения. Поток пользовательского интерфейса должен иметь исключительный доступ к данным, с которыми он работает, коммуникационным процедурам должна предоставляться копия этих данных, а взаимодействие между обеими системами должно осуществляться лишь в строго определенных и надежно протестированных точках.
2. Тестируйте коммуникационные процедуры, вызывая их в синхронном режиме. Как уже отмечалось ранее, тестировать и отлаживать коммуникационный код намного проще, если он выполняется в синхронном режиме потоком пользовательского интерфейса. По этой причине гораздо целесообразнее сначала выявить все ошибки в коде, выполняющемся синхронно с пользовательским интерфейсом, и лишь после этого переходить к тестированию кода при его выполнении фоновым потоком. Я рекомендую вам помещать на форму большую кнопку с надписью "Тестировать и сохранить код" и использовать ее для тестирования и отладки своих коммуникационных процедур. Причина, по которой я рекомендую использовать именно "большую кнопку", заключается в том, что такая кнопка выглядит уродливо и бросается в глаза, так что вы никогда не забудете удалить ее.
3. Ужесточайте условия тестирования своих коммуникационных процедур, вызывая их в асинхронном режиме в намеренно затрудненных ситуациях, используя тестовое приложение. Код, выполняемый фоновым потоком, всегда оказывается сложнее синхронно вызываемого кода. Кроме того, отслеживать все тонкие разновидности повреждения данных и логические ошибки в управлении состоянием, возникающие в условиях, когда несколько различных потоков взаимодействуют между собой самым непредсказуемым образом, весьма нелегко, что дополнительно осложняется наличием многофункционального кода приложения, который окружает отлаживаемый код. Лучший способ создания действительно надежных систем — это жесткое тестирование кода в упрощенном окружении, предназначенном для помещения кода в специально осложненные состояния, и мониторинг выполнения кода для выявления необычных ситуаций. Целесообразно создать специальное приложение, в котором несколько потоков кода выполняются асинхронно, и тщательно исследовать внутреннее состояние приложения для выявления любых возможных неожиданных результатов. Тестирование такого рода может указать вам на необходимость введения некоторых ограничений в выполняемый код, которые не позволят приложению переходить в состояния повышенного риска; так, код, предназначенный для выполнения фоновой задачи, может активно препятствовать выполнению нескольких экземпляров этой задачи параллельными потоками, если вы обнаружили, что надежность такого выполнения является проблематичной, а использование многопоточности не является обязательным. Тестированный, усовершенствованный и отлаженный подобным образом код можно включать в код приложения с гораздо большей уверенностью, нежели код, который считается заведомо корректным исключительно на основании голого оптимизма.
4. Проведя тестирование кода, немедленно преобразуйте коммуникационные процедуры, чтобы они могли выполняться в рабочем фоновом потоке вашего приложения. После отладки базового коммуникационного кода в синхронном режиме и его тестирования в асинхронном режиме он будет готов к помещению в асинхронную операцию, выполняемую в вашем приложении. Приложение должно основываться на понятной и согласованной модели выполнения кода рабочим фоновым потоком, и эту же модель следует применять для всех ваших асинхронных коммуникационных потребностей. Эту модель асинхронного выполнения всегда необходимо использовать и при встраивании коммуникационного кода в пользовательский интерфейс приложения. Не встраивайте синхронные вызовы коммуникационных процедур в пользовательский интерфейс, планируя впоследствии, когда у вас появится время, преобразовать их для выполнения в асинхронном режиме; чем больше кода вы сюда поместите, тем больше зависимостей будет создано.
Как правило, гораздо проще взять в качестве исходной асинхронную систему и выполнять ее в синхронном режиме, чем поступать наоборот. Чтобы синхронно выполнить асинхронную систему, ваше приложение должно просто вызвать асинхронный код и начать выполнять цикл ожидания, выход из которого и выполнение дальнейшего кода осуществляются после завершения выполнения асинхронного кода. Чтобы превратить синхронное взаимодействие в асинхронное, вы должны идентифицировать все состояния приложения, которые затрагиваются коммуникационными процедурами, и создать отдельные копии этих данных, вы должны создать модель для фонового выполнения, из которой будет вызываться код, вы должны перепроектировать способ, используемый коммуникационным кодом для взаимодействия с элементами пользовательского интерфейса, поскольку организовать надежный доступ к элементам управления пользовательского интерфейса из других потоков во многих случаях невозможно (эта ошибка, несомненно снижающая надежность приложения, является весьма распространенной при использовании .NET Compact Framework! Для организации взаимодействия с элементами управления пользовательского интерфейса из других потоков следует использовать вызов метода <ЭлементУправления>.Invoke()), вы должны разработать способ уведомления пользовательского интерфейса о возникновении проблем в процессе информационного обмена, вы должны предусмотреть корректную обработку сбойных ситуаций и, наконец, вы должны разработать модель для обмена командами и состояниями между пользовательским интерфейсом и коммуникационным кодом. Сделать все это уже после того, как разработка приложения почти закончена, очень трудно, и любые попытки преобразования синхронных операций в асинхронные в мобильном приложении потребуют, по крайней мере, существенных переделок и, как правило, привнесут в ваше приложение логические ошибки и сделают его работу ненадежной. С самого начала проектируя код, с помощью которого осуществляется информационный обмен с внешними источниками информации, в асинхронной форме, вы получите намного более удовлетворительные результаты.
НА ЗАМЕТКУ
Выполнение кода в фоновом потоке обсуждается в главе 9.
Работайте на самом высоком уровне абстракции, который соответствует вашим потребностям
Как и в случае кода для настольных компьютеров и серверов, целесообразно работать на самом высоком из допустимых уровней абстракции.