Шрифт:
Как правило, гораздо проще взять в качестве исходной асинхронную систему и выполнять ее в синхронном режиме, чем поступать наоборот. Чтобы синхронно выполнить асинхронную систему, ваше приложение должно просто вызвать асинхронный код и начать выполнять цикл ожидания, выход из которого и выполнение дальнейшего кода осуществляются после завершения выполнения асинхронного кода. Чтобы превратить синхронное взаимодействие в асинхронное, вы должны идентифицировать все состояния приложения, которые затрагиваются коммуникационными процедурами, и создать отдельные копии этих данных, вы должны создать модель для фонового выполнения, из которой будет вызываться код, вы должны перепроектировать способ, используемый коммуникационным кодом для взаимодействия с элементами пользовательского интерфейса, поскольку организовать надежный доступ к элементам управления пользовательского интерфейса из других потоков во многих случаях невозможно (эта ошибка, несомненно снижающая надежность приложения, является весьма распространенной при использовании .NET Compact Framework! Для организации взаимодействия с элементами управления пользовательского интерфейса из других потоков следует использовать вызов метода <ЭлементУправления>.Invoke), вы должны разработать способ уведомления пользовательского интерфейса о возникновении проблем в процессе информационного обмена, вы должны предусмотреть корректную обработку сбойных ситуаций и, наконец, вы должны разработать модель для обмена командами и состояниями между пользовательским интерфейсом и коммуникационным кодом. Сделать все это уже после того, как разработка приложения почти закончена, очень трудно, и любые попытки преобразования синхронных операций в асинхронные в мобильном приложении потребуют, по крайней мере, существенных переделок и, как правило, привнесут в ваше приложение логические ошибки и сделают его работу ненадежной. С самого начала проектируя код, с помощью которого осуществляется информационный обмен с внешними источниками информации, в асинхронной форме, вы получите намного более удовлетворительные результаты.
НА ЗАМЕТКУ
Выполнение кода в фоновом потоке обсуждается в главе 9.
Работайте на самом высоком уровне абстракции, который соответствует вашим потребностям
Как и в случае кода для настольных компьютеров и серверов, целесообразно работать на самом высоком из допустимых уровней абстракции.
Например, если в процессе работы с Internet-протоколами у вас есть возможность работать на уровне Web-служб, используя запросы/ответы SOAP, то именно так вам и следует поступить; встроенные абстракции позволят вам сэкономить массу времени, превращая ваши Web-запросы в простые вызовы методов. Если для повышения производительности приложения или в интересах его пользовательской адаптации вам необходимо спуститься вниз на один уровень, то запросы и ответы HTTP или HTTPS обеспечат вам довольно высокий уровень абстракции и, как правило, дружественность по отношению к брандмауэрам. Если окажется, что запросы/ответы HTTP не в состоянии удовлетворить ваши потребности, у вас есть возможность использовать средства коммуникации на уровне сокетов и создаваемые поверх сокетов потоки.
Работа на уровне абстракции сокетов усложняет код, поскольку для взаимодействия с серверами вам придется спроектировать собственные коммуникационные протоколы, а не использовать простые и надежно тестированные механизмы запросов/ответов HTTP. Если ваше приложение должно связываться с сервером, который требует взаимодействия на уровне сокетов, то рекомендуется рассмотреть возможность создания прокси-компонента на стороне сервера, который взаимодействовал бы с интерфейсом сокета и, в свою очередь, предоставлял вашему приложению интерфейс HTTP или Web-служб. Поскольку взаимодействие сервера с сервером обычно характеризуется более высокой надежностью по сравнению с взаимодействием между устройством и сервером, то описанная мера позволяет значительно упростить код на стороне устройства и повысить надежность вашего мобильного приложения.
Работать на коммуникационных уровнях ниже уровня сокетов, а также непосредственно использовать стек протоколов TCP/IP имеет смысл лишь в крайних случаях. Если вашему приложению необходимо организовать информационный обмен на этом уровне, то для работы с протоколами вам, вероятнее всего, придется написать большой объем собственного кода на языке С. Дополнительная сложность кода и необходимость его тщательного тестирования почти никогда не стоят того выигрыша, который от этого получит приложение. Высокоуровневые протоколы надежно тестируются, и для того чтобы обеспечить ту же степень надежности при использовании собственных коммуникационных протоколов, вам надо будет проделать невероятно большой объем работы. То же самое справедливо и в отношении протоколов, отличных от протокола TCP/IP; возможно, протокол TCP/IP и не является самым идеальным коммуникационным механизмом для всех задач, но обеспечить своими силами тот же уровень тестирования и надежности кода, что и для этих стеков протоколов, очень трудно. Если только вы не намереваетесь предложить совершенно новый коммерческий протокол, проектирование и тестирование которого потребуют от вас огромных усилий, то нет никакого резона заново изобретать колесо, пытаясь получить идеальный протокол, который будет использоваться исключительно для ваших собственных нужд. Использовать 80% вполне пригодного "колеса", которое уже существует и прошло многолетнее тестирование, намного лучше, чем пытаться создать новое "колесо" самому. Прежде чем приниматься за изобретение нового коммуникационного протокола или переходить на использование более низкого уровня в стеке коммуникационных протоколов, вы должны удостовериться в том, что существующие высокоуровневые коммуникационные протоколы не в состоянии удовлетворить запросам вашего мобильного приложения.
Всегда исходите из того, что связь может быть нарушена в любой момент
При написании коммуникационного кода ключевую роль должно играть обеспечение его отказоустойчивости. Традиционные коммуникационные технологии часто описываются в виде многоуровневого стека, который начинается физическим уровнем, далее содержит канальные уровни и уровни протоколов и заканчивается уровнем приложения. Большинство этих уровней аналогичным образом работают и на мобильных устройствах. Как правило, для каждого уровня предусмотрены встроенные средства повышения отказоустойчивости, вызываемые при обнаружении ошибок или возникновении незначительных сбоев в процессе связи. В большинстве случаев вам не следует беспокоиться о специфике нижних коммуникационных уровней; ситуация здесь в значительной степени та же, что и при написании кода для настольных компьютеров и серверов. Единственное различие состоит в том, что в мобильных сетях нарушения связи случаются чаще, чем в кабельных или стационарных беспроводных сетях.
При написании отказоустойчивого коммуникационного кода очень важно тщательно следить за тем, как освобождаются ресурсы в случае возникновения каких-либо нарушений нормального режима работы. Организация связи представляет собой сложный процесс, включающий многоступенчатое установление множества соединений и распределение системных ресурсов. В случае возникновения сбоев в процессе связи важно вовремя освободить системные и иные ресурсы, удерживаемые вашим приложением, а если вы используете .NET Compact Framework, то обязательно осуществите профилактический вызов метода Dispose для освобождения тех ресурсов, которые поддерживают данный метод.
Вызов метода Dispose имеет очень большое значение, поскольку это приводит к немедленному освобождению соответствующих системных ресурсов еще до того, как сборщик мусора закроет ненужные дескрипторы и освободит заблокированные ресурсы. В данной ситуации вам может очень пригодиться ключевое слово С# using (например, using(myObject) {...ваш код...}), поскольку его использование гарантирует вызов метода Dispose не только в случае успешного выполнения определяемого им блока кода, но и при возникновении исключений в этом блоке. Важно отметить, что некоторые классы .NET, такие как System.Net.Sockets.Socket, не имеют общедоступного метода Dispose, однако для них предусмотрен метод Close, который и следует вызывать для освобождения ресурсов, удерживаемых объектом. Вы должны внимательно изучить имеющуюся документацию по всем коммуникационным объектам, которые используете, чтобы иметь полную уверенность в том, что все правила и процедуры, используемые для восстановления ресурсов этих объектов, вам понятны.
Если при обработке сбойных ситуаций вы непреднамеренно оставите ресурсы открытыми, то тем самым создадите предпосылки для сбоя при последующей попытке установления соединения. Если необходимые операции по закрытию ресурсов не выполнены, то разрыв сетевого соединения в результате сбоя, вероятнее всего, приведет к ситуации, в которой последующие попытки установления соединения окажутся неудачными, поскольку необходимый локальный ресурс перед этим был оставлен открытым в состоянии исключительного доступа и поэтому не может быть повторно открыт.