Выбрать главу

Подход, основанный на использовании курсора ожидания, является не совсем уместным, если либо 1) ожидаемая продолжительность выполнения задачи настолько велика, что длительное отображение этого курсора лишь усилит раздражение пользователя, либо 2) длительность выполнения задачи неизвестна или не ограничена, что бывает при доступе к внешним ресурсам устройства. В обоих случаях проанализируйте возможность использования фоновых потоков. 

Максимально упрощайте многопоточный код и документируйте его для повышения надежности

Безопасность многопоточной поддержки — штука хитрая. Если не уделить должное внимание тому, как осуществляется считывание и запись переменных-членов, то может случиться так, что ваше приложение будет пытаться прочитать в одном потоке переменную, запись которой была начата другим потоком, но еще не успела закончиться; "атомарность", то есть неделимость — выполнение за один раз от начала до конца, для большинства операций над данными, находящимися в памяти, не гарантируется, поскольку для записи большинства типов данных требуется выполнение нескольких инструкций микропроцессора. Тот факт, что возникновение проблем подобного рода зависит от временных характеристик выполнения потоков и случается редко, значительно затрудняет их обнаружение, воспроизведение и отладку. Даже ecли гарантирована атомарность доступа к переменным, но при этом было уделено недостаточное внимание тому, как осуществляются вызовы функций-членов классов, то вы можете оказаться в ситуации, когда либо портятся данные, либо программа ведет себя непредсказуемым образом, поскольку соответствующие данные параллельно изменяются алгоритмами, выполняющимися разными потоками; представьте, например два потока, которые одновременно пытаются вставлять и удалять записи в одном и том же связанном списке. Для надежной обработки таких ситуаций необходимо определить "критические разделы" кода; тем самым будет гарантироваться, что любой код, связанный с одним и тем же объектом семафора, сможет выполнять только одним потоком. (В C# это достигается за счет использования оператора lock(объект), а в Visual Basic — с использованием оператора SyncLock(объект). Для получения более подробной информации относительно двух указанных операторов обратитесь к библиотеке справочной документации MSDN.) Ситуацию могут еще более осложнять "зависания", или "взаимоблокировки", когда два потока, выполняющиеся в одно и то же время в разных критических разделах, вызывают код, который должен войти в критический раздел, "принадлежащий" в данный момент другому потоку; при вхождении в критический раздел другого потока будет приостановлено выполнение каждого потока. По этой причине, а также с учетом факторов производительности, чрезмерно вольное использование критических разделов может привести к появлению дополнительных проблем.

Вы могли бы попытаться сделать все свойства и методы своих классов безопасными в отношении многопоточного выполнения, однако осуществить это чрезвычайно трудно с технической точки зрения и расточительно с точки зрения производительности. В конце концов, весь код вашего приложения оказался бы испещренным множеством самых различных критических разделов и бесчисленным количеством всевозможных объектов, используемых в качестве семафоров совместно с критическими разделами. Код такого типа чрезвычайно трудно проектировать и тестировать; кроме того, он характеризуется повышенными накладными расходами, обусловленными необходимостью осуществления проверок, обеспечивающих безопасность многопоточности, и чрезмерно сериализованным выполнением. Ни в .NET Framework, ни в .NET Compact Framework попытки решения этой задачи не делаются; вместо этого в обеих средах используется подход, основанный на тщательном документировании всех возможностей, и явное объявление того, какие операции безопасны в отношении многопоточного выполнения, а какие таковыми не являются. Предполагается, что разработчики внимательно ознакомятся с документацией и будут ею руководствоваться при использовании классов, свойств и методов. Метод класса, не являющийся безопасным в указанном смысле, не должен вызываться для параллельного выполнения из других потоков. Вместо этого, следует либо создать два различных экземпляра класса, либо сериализовать вызов не являющегося безопасным метода, поместив его в критический раздел. Именно таким способом обеспечивается доступ ко всему, что является необходимым и безопасным, а что таковым не является — документируется.

Аналогичный подход вам следует использовать и в своих проектах. Вы должны явно указывать в коде классы, функции и свойства, требующие доступа из нескольких потоков, и сводить их количество к минимуму. Критические разделы кода следует объявлять и использовать лишь в тех случаях, когда многопоточный доступ является абсолютно необходимым, и обнаружены проблемы параллельного выполнения или доступа к данным, устранить которые простыми методами не удается. Очень тщательно проектируйте, программируйте и тестируйте эти специальные классы и функции и столь же тщательно документируйте все свои классы, свойства и методы. Если многопоточный доступ к типу, свойству или методу не является безопасным или у вас есть сомнения относительно безопасности доступа к ним из нескольких потоков, документируйте это в своем коде. Например: