Программирование мобильных устройств на платформе .NET Compact Framework - Иво Салмре
Шрифт:
Интервал:
Закладка:
Рекомендации по использованию потоков в мобильных приложениях
Назначайте обслуживание пользовательского интерфейса основному потоку
Как ранее уже отмечалось в этой главе, в образном представлении основной поток — это все равно, что портье в хорошей гостинице. Самое главное, чтобы портье всегда находился на своем рабочем месте, оказывая помощь посетителям гостиницы в ответ на их обращения. Когда портье о чем-то просят, он может выполнить эту просьбу самостоятельно, если работа не займет много времени. Задачи, для решения которых требуется длительное время, портье может перепоручать другим сотрудникам гостиницы, чтобы самому иметь возможность незамедлительно обслужить следующего посетителя. Аналогичную модель вы должны использовать и в проекте своего приложения, где в роли портье выступает пользовательский интерфейс приложения, а в роли остальных сотрудников — фоновые потоки.
Стремитесь поддерживать способность пользовательского интерфейса к отклику на высоком уровне
Вашей самой главной проектной задачей должно быть поддержание постоянной готовности пользовательского интерфейса к отклику и интерактивному взаимодействию с пользователем. Это означает, что вы не должны допускать, чтобы пользовательский интерфейс надолго стопорился, и прилагать все усилия к тому, чтобы пользователь всегда был информирован о том, что происходит с его запросом, и имел уверенность в том, что он способен влиять на процесс выполнения фоновых задач.
Начинайте с создания однопоточного приложения
Поскольку введение дополнительных потоков значительно усложняет приложение, используйте их только тогда, когда это диктуется вескими причинами. Старайтесь не поддаваться "очарованию потоками".
В простых случаях пытайтесь обойтись без многопоточного выполнения, используя курсоры ожидания
Курсоры ожидания — это вариант многопоточности "для бедных", который, тем не менее, обладает множеством достоинств, основным из которых является его простота. В этом случае полезная работа не передается фоновому потоку для выполнения в асинхронном режиме, однако, как бы в качестве вежливого извинения перед пользователем за то, что приложение все еще выполняет порученную задачу и ее завершения придется немного подождать, отображается курсор ожидания.
Если вам предварительно известно, в каких местах программы происходит задержка, предусмотрите вывод на экран курсора ожидания, появление которого укажет пользователю на то, что приложение выполняется, а исчезновение — на восстановление способности приложения к интерактивному взаимодействию. Вашей первой линией обороны в плане обеспечения добротного пользовательского интерфейса должно быть уведомление пользователя о том, что в течение ближайшего времени интерфейс не будет реагировать на его запросы. Отображение курсора ожидания — это неплохой способ информирования пользователей о том, что работа выполняется и вскоре будет завершена.
Рассмотрите возможность использования фоновых потоков, если выполнение задачи требует длительного или неопределенного времени
Подход, основанный на использовании курсора ожидания, является не совсем уместным, если либо 1) ожидаемая продолжительность выполнения задачи настолько велика, что длительное отображение этого курсора лишь усилит раздражение пользователя, либо 2) длительность выполнения задачи неизвестна или не ограничена, что бывает при доступе к внешним ресурсам устройства. В обоих случаях проанализируйте возможность использования фоновых потоков.
Максимально упрощайте многопоточный код и документируйте его для повышения надежности
Безопасность многопоточной поддержки — штука хитрая. Если не уделить должное внимание тому, как осуществляется считывание и запись переменных-членов, то может случиться так, что ваше приложение будет пытаться прочитать в одном потоке переменную, запись которой была начата другим потоком, но еще не успела закончиться; "атомарность", то есть неделимость — выполнение за один раз от начала до конца, для большинства операций над данными, находящимися в памяти, не гарантируется, поскольку для записи большинства типов данных требуется выполнение нескольких инструкций микропроцессора. Тот факт, что возникновение проблем подобного рода зависит от временных характеристик выполнения потоков и случается редко, значительно затрудняет их обнаружение, воспроизведение и отладку. Даже ecли гарантирована атомарность доступа к переменным, но при этом было уделено недостаточное внимание тому, как осуществляются вызовы функций-членов классов, то вы можете оказаться в ситуации, когда либо портятся данные, либо программа ведет себя непредсказуемым образом, поскольку соответствующие данные параллельно изменяются алгоритмами, выполняющимися разными потоками; представьте, например два потока, которые одновременно пытаются вставлять и удалять записи в одном и том же связанном списке. Для надежной обработки таких ситуаций необходимо определить "критические разделы" кода; тем самым будет гарантироваться, что любой код, связанный с одним и тем же объектом семафора, сможет выполнять только одним потоком. (В C# это достигается за счет использования оператора lock(объект), а в Visual Basic — с использованием оператора SyncLock(объект). Для получения более подробной информации относительно двух указанных операторов обратитесь к библиотеке справочной документации MSDN.) Ситуацию могут еще более осложнять "зависания", или "взаимоблокировки", когда два потока, выполняющиеся в одно и то же время в разных критических разделах, вызывают код, который должен войти в критический раздел, "принадлежащий" в данный момент другому потоку; при вхождении в критический раздел другого потока будет приостановлено выполнение каждого потока. По этой причине, а также с учетом факторов производительности, чрезмерно вольное использование критических разделов может привести к появлению дополнительных проблем.
Вы могли бы попытаться сделать все свойства и методы своих классов безопасными в отношении многопоточного выполнения, однако осуществить это чрезвычайно трудно с технической точки зрения и расточительно с точки зрения производительности. В конце концов, весь код вашего приложения оказался бы испещренным множеством самых различных критических разделов и бесчисленным количеством всевозможных объектов, используемых в качестве семафоров совместно с критическими разделами. Код такого типа чрезвычайно трудно проектировать и тестировать; кроме того, он характеризуется повышенными накладными расходами, обусловленными необходимостью осуществления проверок, обеспечивающих безопасность многопоточности, и чрезмерно сериализованным выполнением. Ни в .NET Framework, ни в .NET Compact Framework попытки решения этой задачи не делаются; вместо этого в обеих средах используется подход, основанный на тщательном документировании всех возможностей, и явное объявление того, какие операции безопасны в отношении многопоточного выполнения, а какие таковыми не являются. Предполагается, что разработчики внимательно ознакомятся с документацией и будут ею руководствоваться при использовании классов, свойств и методов. Метод класса, не являющийся безопасным в указанном смысле, не должен вызываться для параллельного выполнения из других потоков. Вместо этого, следует либо создать два различных экземпляра класса, либо сериализовать вызов не являющегося безопасным метода, поместив его в критический раздел. Именно таким способом обеспечивается доступ ко всему, что является необходимым и безопасным, а что таковым не является — документируется.
Аналогичный подход вам следует использовать и в своих проектах. Вы должны явно указывать в коде классы, функции и свойства, требующие доступа из нескольких потоков, и сводить их количество к минимуму. Критические разделы кода следует объявлять и использовать лишь в тех случаях, когда многопоточный доступ является абсолютно необходимым, и обнаружены проблемы параллельного выполнения или доступа к данным, устранить которые простыми методами не удается. Очень тщательно проектируйте, программируйте и тестируйте эти специальные классы и функции и столь же тщательно документируйте все свои классы, свойства и методы. Если многопоточный доступ к типу, свойству или методу не является безопасным или у вас есть сомнения относительно безопасности доступа к ним из нескольких потоков, документируйте это в своем коде. Например:
// К ДАННОЙ <ПЕРЕМЕННОЙ/СВОЙСТВУ/МЕТОДУ> ДОСТУП ИЗ НЕСКОЛЬКИХ ПОТОКОВ
// ОСУЩЕСТВЛЯТЬСЯ НЕ ДОЛЖЕН!!!