[ главная ]   [ рейтинг статей ]   [ справочник радиолюбителя ]   [ новости мира ИТ ]



Ответов: 0
25-02-12 07:01







   Web - программирование
PHP


ASP






XML



CSS

SSI





   Программирование под ОС











   Web - технологии








   Базы Данных









   Графика






Данные




Программирование под ОС / Pascal - Delphi /

Понимание потоковых моделей в COM при программировании на Delphi стр.1

Бин Ли


Я всегда твердо верил, что нет ничего такого, что было бы невозможно понять. Необходимо только "выпарить" наиболее сложные части проблемы и потратить какое-то количество времени на понимание частей проблемы "кусок за куском". Затем эти части собрать вместе и понять проблему целиком.

Введение


Потоковые модели в COM имеют репутацию наиболее сложных для понимания. Возможно потому, что множество имеющейся документации по этой теме имеет "техническую природу" или ориентировано на конкретный язык, чаще всего C или C++. Цель этой статьи - дать Вам возможность понять, почему потоковые модели в COM так важны и как правильно использовать потоковые модели в Ваших приложениях COM. Моя цель - представить Вам материал таким образом, чтобы Вы могли читать его последовательно от начала до конца и в результате понять всю статью. Сказав это, я бы настойчиво рекомендовал Вам не пропускать ни одной страницы в процессе чтения, чтобы у Вас не возникло трудностей оттого, что Вы что-то пропустили раньше. А теперь, я желаю Вам удачи, и не говорите, что я не предупреждал Вас об этом!

Прежде, чем начать изложение, давайте начнем с того, что поймем, почему потоковые модели так важны для Ваших приложений COM. Исходя из своего опыта, я могу сказать, что наиболее существенной причиной использования потоковых моделей является повышение общей производительности и скорости реакции Вашей программы, особенно для объектов серверов COM, которые используются для обслуживания большого количества клиентских приложений. Но я не хочу сказать, что использование потоковых моделей в Ваших объектах серверов COM всегда увеличивает производительность. Вы должны тщательно изучить, как используются Ваши объекты и как потоковая модель повлияет на производительность приложения и целостность данных. Я должен подчеркнуть, что вопросы целостности обязательно должны рассматриваться объектов при принятии решения, применять или нет потоковую модель.

Несмотря на то, что Вы можете думать, что использование потоковой модели существенно повысит производительность объекта, может оказаться, что Ваши объекты сильно зависят, скажем, от третьих библиотек, которые могут "не выжить" в условиях многопоточности. Другой хорошей причиной применения многопоточности может быть то, что задача по своей природе является весьма пригодной для многопоточной реализации. Например, серверные объекты, являющиеся чисто служебными объектами, вероятно, могут сильно зависеть от времени при выполнении операций или захватывании ресурсов. Примерами таких объектов являются мониторы работы оборудования, объекты пакетной обработки или даже простые объекты манипулирования данными, время исполнения которых для успешного завершения непредсказуемо. В этих случаях тип разрабатываемого Вами приложения по существу определяет использование многопоточности.

Имеется множество других причин, при которых Вы могли бы использовать многопоточность, но две упомянутые выше причины являются наиболее общими среди наблюдаемых в промышленном программировании.

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

Основы многопоточности и COM


Многопоточность в COM очень легко понять. На самом деле! Нужно всего лишь потратить время на освоение большого количества новой информации! Одной из "непростительных" причин того, что изучение многопоточности в COM столь трудно, является страх перед хорошо звучащими (но непонятными) словечками, такими как:

подразделения (apartments), однопоточные подразделения (STAs), многопоточные подразделения (MTAs), свободное использование потоков (free threading), маршалинг интерфейса (interface marshaling) и т.д.

Но это все только громко звучащие слова и ничего более. В действительности же имеется множество хорошо определенных правил, которым очень легко следовать для полного понимания многопоточности в COM. Все, что Вам нужно - это знать, что это за правила, выучить их, а затем действительно использовать их в своих приложениях. Я бы хотел начать разговор о первом и наиболее общем правиле, а затем идти дальше и дальше к специфическим правилам далее в этой статье.

Правило #1: Каждое приложение, использующее COM, должно сообщить COM, как оно будет управлять потоками исполнения (никаких если, никаких но). Это правило является важным, так как для того, чтобы COM могла бы взаимодействовать с Вашим приложением или Ваше приложение могло бы взаимодействовать с другими приложениями посредством COM, COM должна знать как правильно делать вызовы объектов Вашего приложения на уровне той потоковой модели, которую Вы указали.

Это имеет смысл, так как COM является тем клеем, который используется приложениями для взаимодействия друг с другом и обеспечивает корректность взаимодействия многопотоковых приложений. COM, по крайней мере, должен знать, как приложения, которые он связывает вместе, управляют многопоточностью. Этот уровень "потоковой трудности" называется потоковой моделью COM.

Для целей этой статьи мы определим 3 потоковых модели. В действительности нет никакой разницы, как их назвать. И так как мы понимаем, что каждая из них представляет из себя, я не буду больше беспокоиться о том, как Вам хотелось бы их называть.

Однопотоковая модель (the single-threaded model):


Если приложение работает в однопотоковой модели, то это означает, что в этом приложении имеется только один поток (thread) исполнения, в котором приложение взаимодействует с COM.

Это подразумевает, что COM будет гарантировать взаимодействие с Вашим приложением (производить обращения к нему) таким образом, что не будет одновременных обращений в разных потоках. Когда COM хочет чего-либо от Вашего приложения, он будет производить это только в главном потоке Вашего приложения.

Это также подразумевает, что если у Вас есть однопотоковый сервер COM, который сейчас имеет, скажем, 50 объектов, используемых (разделяемых) 50-ю клиентами и, если все 50 клиентов пытаются вызвать метод каждого объекта в одно и то же время, то COM вмешается и не позволит выполниться 50 потокам на Вашем сервере одновременно. Вместо этого COM сделает так, что вызовы методов будут произведены в одном потоке один после другого, пока все 50 объектов не удовлетворят вызовы 50 методов (и, конечно же, каждый метод будет возвращать результат работы непосредственно после своего завершения).

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

Верите или нет, но Delphi 3 технически может создавать только однопотоковые внутренние и внешние сервера COM. Хотя это не может выглядеть ограничением для внутренних серверов (DLL), но, поверьте мне, это очень плохо работает в случае с внешними серверами (EXE), когда множество клиентов должны обслуживаться одновременно с разумным временем отклика.

Модель однопотокового подразделения (the single-threaded apartment model - STA):


Не вдаваясь в подробности сейчас (эта модель будет полностью объяснена позже), эта модель позволяет COM взаимодействовать с Вашим приложением в нескольких подразделениях (apartment), каждое из которых содержит в точности один поток(thread).

Вы можете подумать: Ух ты! Что за чертовщина это подразделение? Простейшее определение, которое я видел, звучит так: подразделение - это хорошо определенная инкапсуляция того, как потоки и объекты COM взаимодействуют друг с другом. Когда я говорю "инкапсуляция", я подразумеваю, что некоторый поток и некоторый объект, которые должны что-то делать совместно с COM определены в понятии "подразделение", в котором они "живут". Другими словами, Вы не можете описать как поток и объект COM взаимодействуют друг с другом без определения сначала понятия "подразделение", которое содержит как поток, так и объект.

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

Правило #2: Каждый поток в Вашем приложении, использующий COM, сначала должен войти в подразделение или инициализировать его (никаких если, никаких но).

Если поток вошел в подразделение или инициализировал его, он должен покинуть его до того как завершится сам (никаких если, никаких но). Просто полагайте, что подразделение - это то место, где "живет" поток.

Причиной того, почему поток, использующий COM, должен войти в подразделение, является тот факт, что COM должен внутренне инициализировать себя таким образом, чтобы знать, как правильно взаимодействовать с потоком.

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

Если поток входит в подразделение и затем создает объект COM, то этот объект описывается как "расположенный" или "живущий" в этом подразделении (в действительности это не совсем аккуратное определение, но для целей простоты и легкости понимания подразделений давайте сейчас предполагать, что это так). Теперь, если различные потоки (каждый из которых может быть, а может и не быть потоком, первоначально создавшим объект) пытаются манипулировать этим объектом, COM может вмешиваться в их работу с полной уверенностью, что доступ к этому объекту производится именно тем способом, который определяется типом подразделения, в котором он живет.

В чем же заключаются правила однопотокового подразделения? Очень просто!

Во-первых, однопотоковое подразделение (STA) - это подразделение, содержащее только один поток, взаимодействующий с COM.

Во-вторых, если различные потоки пытаются взаимодействовать с STA или, точнее, с объектом COM, живущим в STA, COM будет гарантировать, что все потоки будут обслуживаться по принципу "только один одновременно", единственным потоком, который первым вошел или, что существенно, создал это подразделение.

В-третьих, если Ваше приложение желает использовать модель однопотокового подразделения и потребуется взаимодействие нескольких потоков с COM, то Ваше приложение будет вынуждено создать несколько однопотоковых подразделений, каждое из которых соответствует единственному потоку. Чтобы легче было запомнить эти правила, давайте я приведу пример.

Пусть Вы создали объект COM X в потоке STA. Пусть X имеет один метод с именем Method1. Теперь, если два или более потоков (очевидно, что каждый из них живет в своем STA) пытаются одновременно вызвать X.Method1 в одном экземпляре X, COM в соответствии с моделью STA, будет гарантировать, что X в действительности не бомбардируется одновременными вызовами Method1 в разных потоках. Вместо этого COM обеспечит, что X.Method1 будет вызван первым запросившим потоком, затем следующим, затем еще следующим и т.д. пока ВСЕ вызовы не будут обслужены одним потоком, который первоначально создал подразделение, в котором живет X.

Другими словами, вызовы методов объекта COM, живущего в STA, гарантировано будет следовать последовательно один после другого вне зависимости от того, откуда пришел этот поток.

Теперь, если Вы создадите два экземпляра объекта X, каждый в отдельном STA и вызовите метод X.Method1 в обоих экземплярах одновременно, COM позволит обоим вызовам исполняться одновременно, так как каждый раздельный STA имеет свой собственный поток.

Поэтому основная разница между моделью STA и однопотоковой моделью заключается в том, что при однопотоковой модели может быть только один поток на все приложение, в то время как в модели STA Вы можете иметь несколько потоков, каждый из которых исполняется в собственном STA. Если Вы немного подумаете, то придете к выводу, что однопотоковая модель - это все лишь дегенеративный вариант модели STA, в которой имеется только один STA на все приложение.

Модель многопотокового подразделения (the multithreaded apartment model - MTA):


Модель многопотокового подразделения (MTA) просто является расширением модели STA. Если Вы еще способны следить за мной, легко сделать вывод, что MTA является подразделением, в котором могут размещаться несколько потоков, в то время как в STA в подразделении может размещаться только один поток.

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

Как Вы можете видеть, программирование объектов, которые Вы собираетесь использовать в модели MTA, должно осуществляться таким образом, чтобы они могли обрабатывать поступающие запросы от любого потока в любое время и, в то же время, сохранять свою целостность. Другое правило, которое Вы должны всегда помнить, заключается в том, что один процесс, взаимодействующий с COM, может содержать только один MTA. Это означает, что если Вам необходимо создать несколько потоков, взаимодействующих с COM в MTA, то каждый из этих потоков должен входить в единственный MTA Вашего процесса.

Мы только что определили 3 различных типа потоковых моделей, доступных в COM.

Однопотоковая модель может быть описана как самая слабая из всех, когда дело касается использования потоков в приложении, т.е. она не позволяет обрабатывать одновременные запросы COM из разных потоков.

Модель однопотокового подразделения (модель STA) немного сложнее, чем однопотоковая модель. Это выражается в том, что она позволяет взаимодействовать COM с несколькими потоками, но может сделать это только путем создания нескольких подразделений. Это подразумевает, что STA не является существенно более сложной, чем однопотоковая модель. При этом каждый STA имеет только один поток, который и будет обрабатывать все взаимодействие с COM в пределах подразделения.

MTA, с другой стороны, является наиболее сложным из всех перечисленных в смысле взаимодействия с COM нескольких потоков. Другими словами, объект, живущий в MTA, говорит внешнему миру "Я сложно устроен! Я могу работать с потоками где угодно и в любое время!".

Перед тем, как продолжить дальше, я бы хотел отметить некоторые термины.

Модель STA часто называется проще как "работающие в одном потоке" (apartment-threaded), а модель MTA как "работающие во многих потоках" (free-threaded). Хотя мне и не хочется использовать эти термины в своей статье, но чувствуйте себя свободно и подменяйте названия моделей STA и MTA на них, если они понятнее для Вас. Это только слова и ничего более!

Правило #3: Объекты COM создаются в потоках, потоки живут в подразделениях, а подразделения живут в процессах.

Первое интуитивно понятно: объект COM может существовать только в том случае, если он создан, объект COM может быть создан только в том случае, если поток желает создать его.

Второе легко понять, применив Правило #2, т.е., если каждый поток, который нуждается во взаимодействии с COM, должен войти в подразделение или инициализировать его, то, следовательно, это подразделение, так сказать, является вместилищем потока.

Третье, подразделения живут в процессах, может не очень легко "визуализовать" до тех пор, пока Вы не начнете писать код, взаимодействующий с подразделениями. Не беспокойтесь, что Вы пока не согласны со мной в том, что подразделения живут в процессах, это придет позже, когда у Вас появится некоторый опыт, а сейчас просто запомните это правило, так как оно понадобится для понимания нижеследующих концепций.

Правило #4: Если клиентское приложение COM задает потоковую модель, не того типа, т.е. несовместимую с типом потоковой модели сервера COM, сам COM гарантирует, что клиент и сервер будут все равно правильно взаимодействовать друг с другом. Другими словами, COM будет учитывать мнение клиента о том, как он может управлять потоками, и также COM будет учитывать мнение сервера о том, как он может управлять потоками. COM возьмет на себя ответственность за "бесшовную" совместную работу клиента с сервером без каких-либо дополнительных усилий по разработке со стороны программиста. Но Вы можете спросить, как COM может делать это? В действительности ответ очень прост:
Правила #1 и #2 предполагают, что если Вы однажды определили потоковую модель и тип подразделения (подразделений), с которыми работает Ваше приложение, то Вы тем самым заключили обоюдное соглашение с COM в том, что Вы и COM должным образом все подготовили в соответствии с правилами. Например, если Вы говорите COM, что Вы хотите поддерживать только однопотоковую модель, то Вы говорите "Эй, COM. Я гарантирую тебе, что я могу обрабатывать любые запросы только в одном потоке. Если ты нуждаешься во мне, ты можешь делать все только в одном потоке. Не допускай ко мне множественные потоки, так как я не готов обрабатывать их!" COM принимает эти слова как молитву и будьте уверены без дополнительных вопросов, что все Ваши запросы будут удовлетворены. Это означает, что если есть два приложения, клиент и сервер, каждое из которых сказал COM, что он может и что не может делать, то COM обезопасит Вас от кровавых разборок между ними, обеспечив тем самым, что запросы будут правильно восприниматься с обоих сторон, когда они начнут взаимодействовать друг с другом. Если COM не делал бы этого, то потоковая работа COM была бы сущим кошмаром. Подумайте об этом!

Так, где это мы сейчас? Мы установили очень важные базовые правила поведения потоков в COM. Если Вы успели заметить, все это было чистой теорией. Я умышленно сделал это, так как когда я исследовал и изучал эту тему, мне пришлось пройти через большое количество программного кода, который не имел никакого смысла. Поверьте мне, Вы не хотите этого делать и даже использовать его до тех пор, пока Вы не поймете наконец, что этот код делает. Если Вы делали это, то Вы уже познакомились с потенциальными проблемами в Ваших приложениях, и Вы можете насчитать множество часов, проведенных за отладкой, пока Вы однажды не открыли для себя, что использование потоков в COM чревато трудно находимыми ошибками. В конце концов я понял, что следует усвоить основы прежде, чем написать некий код, который будет что-либо делать с потоками в COM. Теперь я бы хотел, чтобы Вы внимательно перечитали все то, что мы уже узнали, чтобы быть уверенным, что Вы, по крайней мере, поняли, о чем же идет речь. Если Вы чувствуете себя уверенно после изучения первых четырех правил, то Вы должны быть готовы двигаться дальше к следующим частям, где мы сконцентрируемся на специфических деталях, окружающих первые четыре правила. Готовы?

Модель однопотокового подразделения (the Single Threaded Apartment Model - STA)


В предварительном обсуждении мы установили, что модель STA позволяет Вам разрабатывать многопотоковые приложения COM, в которых каждый поток должен инициализироваться/содержаться внутри своего собственного подразделения. Мы также поняли, что для того, чтобы поток правильно взаимодействовал с COM, он должен сначала войти в подразделение или инициализировать его. Так как же именно поток входит в STA? COM предоставляет функцию CoInitializeEx API для входа потока в подразделение. Синтаксис CoInitializeEx (определенный в ActiveX.pas) следующий:

function CoInitializeEx (pvReserved : pointer; coInit : longint) : HResult; stdcall; Параметр pvReserved в данном случае бесполезен и должен быть равен NIL. Параметр coInit определяет тип подразделения, в который поток желает войти. Для модели STA Вы должны передавать в качестве этого параметра константу COINIT_APARTMENTTHREADED. Короче, следует писать следующий вызов: CoInitializeEx (NIL, COINIT_APARTMENTTHREADED); который позволяет потоку Вашего приложения войти в подразделение STA (инициализировать его). Мы также уже знаем, что если поток вошел в подразделение, то он должен выйти из него до своего завершения. Для выхода из подразделения COM предлагает следующий вызов CoUninitialize API, имеющий следующий синтаксис. procedure CoUninitialize; stdcall; Если поток производит вызов CoInitializeEx дважды подряд, то в первом случае возвращается код завершения функции (HResult), равный S_OK. Во второй раз (и во все последующие) будет возвращено значение S_FALSE, при этом никаких действий не производится. Хотя некоторые авторы и не рекомендуют производить лишние вызовы CoInitializeEx (Прим. перев. - так у автора; вероятно должно быть CoUninitialize), мои исследования показывают, что Вам все равно следует обращаться к CoUninitialize ДЛЯ КАЖДОГО вызова CoInitializeEx вне зависимости от того, вернул ли предыдущий вызов S_OK или S_FALSE. Это склоняет меня к мысли, что исполняющая система COM ведет счетчик количества входов в STA одного и того же потока, и позволяет потоку успешно покинуть STA только в том случае, если счетчик входов равен нулю. Например: begin
  // вход в STA
  CoInitializeEx (NIL, COINIT_APARTMENTTHREADED);
  // этот второй вызов организует повторный вход в текущий STA
  // (возвращает код завершения )
  CoInitializeEx (NIL, COINIT_APARTMENTTHREADED);
  // этот вызов закрывает последний CoInitializeEx,
  // но выход из STA не производится
  CoUninitialize;
  // этот вызов закрывает первый CoInitializeEx
  // и окончательно покидает STA CoUninitialize;
end;
После входа потока в подразделение недопустимо изменять (или входить по-другому) подразделения до тех пор, пока поток не покинет это подразделение. Это означает, что если поток вошел в STA, а затем производит вызов CoInitializeEx, осуществляющий вход в MTA (вход в MTA обсуждается позже в данной статье), последний вызов CoInitializeEx не будет успешным и вернет код своего завершения, равный RPC_E_CHANGED_MODE ($80010106), означающий "Нельзя изменять тип потока после того, как он был установлен" ("Cannot change thread mode after it is set"). В этом случае Вы не должны производить вызов соответствующего CoUninitialize. Другими словами вызов CoUninitialize должен производиться только в том случае, если вызов CoInitializeEx вернул S_OK или S_FALSE. Если он вернул что-либо другое Вы не должны вызывать CoUninitialize. COM предлагает и более ранний вызов API - CoInitialize, который функционально эквивалентен CoInitializeEx(NIL, COINIT_APARTMENTTHREADED). Синтаксис CoInitialize следующий: function CoInitialize (pvReserved : pointer) : HResult; stdcall; Таким образом, следующий вызов очень похож на вызов CoInitializeEx, показанный ранее: CoInitialize(NIL); Другая пара ранних вызовов API, достойная внимания - это OleInitialize и OleUninitialize. OleInitialize эквивалентен CoInitialize, но он инициализирует также и подсистему пользовательского интерфейса COM (такую как "тащи и бросай - drag-and-drop" OLE). OleUninitialize эквивалентен вызову CoUninitialize, но он дополнительно деинициализирует подсистему пользовательского интерфейса COM, инициализировавшуюся вызовом OleInitialize. Это означает, что если Ваше приложение COM использует, скажем, OLE drag-and-drop вызов CoInitialize непригоден; Вам необходимо использовать вызов OleInitialize.

Одно Вам необходимо запомнить. Вызов CoInitializeEx возможен только в DCOM для Windows NT4 или если установлено расширение DCOM для Windows 95. Если Вы производите вызовы CoInitializeEx на машине, не поддерживающей указанных расширений DCOM, Вы будете получать сообщение об ошибке, что данный вызов не поддерживается, а Ваша программа не будет работать. Следовательно, если Вы интересуетесь только моделью STA, использование пары вызовов CoInitialize/CoUninitialize будет давать хорошие результаты вне зависимости от того, установлены у Вас расширения DCOM или нет. Если Вы удивляетесь, почему Вам никогда не требовалось производить вызовы CoInitialize/CoUninitialize в Ваших однопотоковых приложениях на Delphi, то это потому, что они уже были выполнены за Вас модулем ComObj.pas. Модуль ComObj производит вызов CoInitialize(NIL) во время своей инициализации, а вызов CoUninitialize - во время финализации. Это означает, что главный поток Вашего приложения всегда живет в STA.

Важно отметить, что если Вам необходимо создавать другие потоки STA в Вашем приложении, взаимодействующем с COM, то Вы должны делать вызовы CoInitialize/CoInitializeEx/CoUninitialize явно, как это было описано выше. Если Вы забываете сделать это, то получаете ошибочный код завершения CO_E_NOTINITIALIZED ($800401F0), означающий "Не был вызван CoInitialize" ("CoInitialize has not been called") из потока, начинающего взаимодействие с COM.

Мы узнали, что STA должен содержать в точности один поток, и, что в случае попытки множества потоков взаимодействовать с объектом COM, живущим в STA, COM будет уверен, что эти потоки могут обслуживаться только одним потоком, живущим в этом STA, в данный момент времени. Проще, COM всегда будет осуществлять все вызовы в потоке STA последовательно. Этот последовательный процесс возможен потому, что (для STA) COM автоматически создает скрытое системное окно, управляющее всеми системными вызовами для каждого ассоциированного STA. Так в действительности, когда нескольким потокам необходимо произвести одновременные обращения к объекту, живущему в STA, COM странслирует эти вызовы в окно сообщений и поставит эти сообщения в очередь один за другим, т.е. в очередь сообщений одного потока, живущего в Вашем STA. Таким образом, сообщения в очереди могут быть проверены и исполнены по одному Вашим потоком STA. Ваш поток будет всего-навсего пропускать (или передавать) эти сообщения скрытому окну для дальнейшей обработки. Это подводит нас к наиболее важному правилу для STA:

Правило #5: Для того, чтобы сервер STA работал бы правильно, его поток должен включать в себя цикл, непрерывно проверяющий наличие оконных сообщений (windows messages) и, соответственно, их обрабатывающий. Другими словами, следующий фрагмент показывает основы реализации работающего потока STA:

var
  rMsg : TMsg;
begin
  CoInitializeEx (NIL, COINIT_APARTMENTTHREADED);
  { главный цикл сообщений потока }
  while (GetMessage (rMsg,0,0,0)) do DispatchMessage (rMsg);
  CoUninitialize;
end;
Если Вы планируете создать поток STA, не имеющий цикла обработки оконных сообщений, Вы должны учитывать, что если Вы пытаетесь получить доступ к объекту, созданному в этом STA, из других потоков (очевидно, в других STA), Ваши вызовы не будут успешно выполнены и Ваш вызывающий поток "зависнет". Очевидно, что причина происходящего кроется в том, что мы только что узнали. Я бы хотел подчеркнуть также, что цикл сообщений необходим только с той точки зрения, что Ваш поток STA собирается обслуживать объекты для потоков в других подразделениях. Действительная цель цикла обработки оконных сообщений заключается в том, что COM может выстраивать последовательно все вызовы, приходящие от всех потоков. Другими словами, если Вы собираетесь создать поток STA, который работает с объектами COM внутри того же самого потока, то нет абсолютно никакой нужды в цикле сообщений. Например, если клиентское приложение создает поток STA и внутри этого потока создает объект COM, работает с ним и затем завершает, то нет никакого смысла в цикле сообщений вообще, т.е. procedure TMySTAThread.Execute;
var
  pObject1 : IObject1;
begin
  CoInitializeEx (NIL, COINIT_APARTMENTTHREADED);
  // Создает объект object1, делает с ним что-то (something) и завершается
  pObject1 := CreateOleObject('Server.Object1') as IObject1;
  pObject1.DoSomething;
  pObject1.DoSomeOtherThing;
  pObject1 := NIL;
  // Цикл сообщений не нужен, так как мы уже все сделали
  CoUninitialize;
end;
Очевидно, что если Вы планируете использовать модель STA в Вашем приложении, то по существу говоря это приложение является многопоточным, в котором каждый поток живет в отдельном STA. Это означает, что для COM имеется возможность делать одновременные вызовы Вашего приложения, если оно создает несколько STA (отметьте существенную разницу: для каждого STA вызовы выстраиваются последовательно, но в целом приложение может иметь несколько STA, вызовы к которым могут поступать одновременно). Это приводит нас к другому правилу, весьма важному для STA:

Правило #6: Благодаря модели STA Ваши объекты STA могут защищать некоторые глобальные (в масштабах приложения) данные, которые могут быть разрушены при одновременном доступе множества объектов из раздельных потоков. Любые данные, характерные для данного экземпляра (обычно это поля, содержащиеся в объявлении класса Вашего объекта), уже являются потоково-безопасными и не нуждаются в защите. Для иллюстрации сказанного рассмотрим следующее:

var
  iGlobal : integer;
type
  TObject1 = class (TAutoObject)
  protected
    FLocal : integer;
    procedure AccessGlobal;
    procedure AccessLocal;
  end;

procedure TObject1.AccessGlobal;
begin
  // Если создается несколько экземпляров TObject1 в раздельных STA и
  // AccessGlobal вызывается в каждом из них одновременно, то возможно,
  // что iGlobal будет испорчена
  // делаем что-либо с глобальной переменной
  iGlobal := iGlobal + 1;
end;

procedure TObject1.AccessLocal;
begin
  // Если создается один экземпляр TObject1 в STA и множество потоков
  // пытаются вызвать AccessLocal в этом единственном экземпляре,
  // то локальная переменная гарантировано не будет испорчена, так как
  // COM обеспечит, что вызовы AccessLocal следуют последовательно
  // друг за другом в пределах одного потока, живущего в этом STA
  // делаем что-либо с локальной переменной
  FLocal := FLocal + 1;
end;
Следовательно, правильным способом исправить TObject1.AccessGlobal является сделать переменную iGlobal защищенной от одновременного доступа из нескольких потоков STA. Простейший способ сделать это - это использовать критические секции Win32, которые обслуживаются операционной системой для таких случаев (я буду полагать, что Вы уже знаете некоторые основы потоков и функций работы с ними, существующие в Win32. Вы должны обратиться к книгам, если Вы чувствуете пробел в своих познаниях в этом месте. Вот книга, которую я нашел достаточно полезной "Win32 Multithreaded Programming"; Cohen и Woodring, O`Reilly, ISBN 1-56592-296-4).
Тогда TObject1.AccessGlobal может быть переписана следующим образом: uses
  SyncObjs;
var
  csGlobal : TCriticalSection;
  iGlobal : integer;

procedure TObject1.AccessGlobal;
begin
  // вход и выход из критической секции теперь гарантирует, что доступ
  // к iGlobal может быть осуществлен одним потоком единовременно и
  // невозможно разрушение при доступе из различных потоков
  csGlobal.Enter;
  try
    // делаем что-либо с глобальной переменной
    iGlobal := iGlobal + 1;
  finally
    csGlobal.Leave;
  end; { finally }
end;

initialization
  csGLobal := TCriticalSection.Create;
finalization
  csGlobal.Free;
end.
Стороннее замечание. Delphi предоставляет в Ваше распоряжение класс TCriticalSection, являющийся оболочкой для примитивов критической секции Win32. TCriticalSection располагается в модуле SyncObjs, который, как я знаю, существует только в версии клиент/сервер Delphi. Если Вы не располагаете приобретенной копией клиент-серверной версии Delphi (как я), не все потеряно:

Вы можете использовать класс TCriticalSection из библиотеки ThreadComLib этой статьи или, если Вы чувствуете в этом силу, использовать вызовы Win32 API.

Подведем небольшой итог. Я как всегда отмечаю, что к объекту COM, живущему в STA можно получить доступ из множества потоков, живущих в других STA и что в случае одновременного доступа к нему все потоки выстраиваются в очередь вне зависимости от того, откуда этот поток пришел. Я Вам еще не сказал, что это гарантируется самим COM, но для того, чтобы COM имел возможность правильно исполнить эту возможность, Вы со своей стороны должны проделать некоторую работу.




Комментарии

 Ваш комментарий к данному материалу будет интересен нам и нашим читателям!



Последние статьи: Программирование под ОС / Pascal - Delphi /

Работа со шрифтами на Win API
06-06-2010   

Сегодня поговорим о шрифтах, и о том, каким образом работать с ними на Win API. Нам потребуется переменная типа HFONT. Изменить стиль шрифта можно у любого компонента, я покажу это на примере кнопки... подробнее

Кол. просмотров: общее - 3302 сегодня - 0

Работа с таймером на Win API
06-06-2010   

Таймер - вещь в хозяйстве очень полезная. Если некое действие нужно повторять с определенной периодичностью, то таймер, это как раз то, что нужно... подробнее

Кол. просмотров: общее - 3343 сегодня - 0

Работа с мультимедийным таймером на Win API
06-06-2010   

Мы уже знакомы с системным таймером, сегодня познакомимся с мультимедийным, основное отличие которого, более высокая скорость (точность) работы... подробнее

Кол. просмотров: общее - 3457 сегодня - 0

Создание CheckBoxов средствами Win API
06-06-2010   

Сегодня наша программа научится работать с CheckBoxами. CheckBox можно представить как флаг, который можно установить или сбросить, и в зависимости от его состояния выполнять определенные действия... подробнее

Кол. просмотров: общее - 3574 сегодня - 2

Создание группы RadioButton средствами Win API
06-06-2010   

В прошлый раз мы сделали несколько радио-кнопок, которые автоматически объединялись в одну группу. Сейчас рассмотрим, как создавать несколько независимых групп радио-кнопок... подробнее

Кол. просмотров: общее - 3382 сегодня - 0



  WWW.COMPROG.RU - 2009-2012 | Designed and Powered by Zaipov Renat | Projects