Леонтьев Б.К. Я изучаю Microsoft Office Visio 2003 (PDF). Удк 004. 738. 5 Ббк 32. 973. 26 018. 2
Скачать 0.88 Mb.
|
COM поддерживает потоки? Если вы разбираетесь в COM, то знаете, что он определяет струк туру соглашения. Объект COM соглашается следовать некоторым прави лам так, чтобы этим можно было успешно пользоваться из любого при ложения или объекта, который поддерживает COM. Большинство людей сначала думает о интерфейсной части согла шения — о методах и свойствах, которые предоставляет объект. Но вы не можете не знать того, что COM также определяет поточ ность как часть соглашения. И подобно любой части соглашения COM — если вы нарушаете эти условия, то возникнут проблемы. Visual Basic, естественно, скрывает от вас большинство механиз мов COM, но чтобы понять как использовать многопоточность в Visual Basic, вы должны разобраться в COM модели потоков. Модель одиночного потока Однопоточный сервер — самый простой тип реализации сервера. И самый простой для понимания. В этом случае EXE сервер выполняет ся в одиночном потоке. Все объекты создаются в этом потоке. Все вызовы методов каждого объекта, поддерживаемого сервером должны прибыть в этот поток. Но что будет, если клиент задействован в другом потоке? В таком случае для объекта сервера должен быть создан промежуточный объект ( proxy object). Этот промежуточный объект выполняется в потоке клиен та и отражает методы и свойства фактического объекта. Когда вызывает ся метод промежуточного объекта, он выполняет операции, необходи мые для подключения к потоку объекта, а затем вызывает метод фактического объекта, используя параметры, переданные к промежуточ ному объекту. Естественно, что этот подход требует значительного вре мени на выполнение задачи, однако он позволяет выполнить все согла шения. Этот процесс переключения потоков и пересылки данных от промежуточного объекта к фактическому объекту и обратно называется marshalling. В случае DLL серверов, одиночная потоковая модель требует, что бы все объекты в сервере создавались и вызывались в том же самом пото ке что и первый объект, созданный сервером. Модель Apartment Threading Модель Apartment Threading как определено COM не требует, что бы каждый поток имел собственный набор глобальных переменных. Какую модель поддерживает ваш сервер? Как приложение или ОС Windows узнает, которую модель потоков использует сервер? Эта информация включена в системный реестр ( reg istry). Когда Visual Basic создает объект, он проверяет системный реестр, чтобы определить, в каких случаях требуется использовать промежуточ ный объект ( proxy object) и в каких — marshalling. Эта проверка является обязанностью клиента и необходима для строгой поддержки требований многопоточности для каждого объекта, который он создает. Функция API CreateThread Теперь давайте посмотрим, как с Visual Basic может использовать ся функция API CreateThread. Скажем, есть в наличии класс, который вы хотите выполнять в другом потоке, к примеру, чтобы выполнить некоторую фоновую опера Использование редактора Visual Basic 337 338 Использование редактора Visual Basic цию. Характерный класс такого типа мог бы иметь следующий код (из примера MTDemo 3): ' Class clsBackground ' MTDemo 3 — Multithreading example ' Copyright © 2003 by Desaware Inc. All Rights Reserved Option Explicit Event DoneCounting() Dim l As Long Public Function DoTheCount(ByVal finalval&) As Boolean Dim s As String If l = 0 Then s$ = "In Thread " & App.threadid Call MessageBox(0, s$, "", 0) End If l = l + 1 If l >= finalval Then l = 0 DoTheCount = True Call MessageBox(0, "Done with counting", "", 0) RaiseEvent DoneCounting End If End Function Класс разработан так, чтобы функция DoTheCount могла неодно кратно вызываться из непрерывного цикла в фоновом потоке. Мы могли бы поместить цикл непосредственно в сам объект, но вы вскоре увидите, что были веские причины для проектирования объекта, как показано в примере. При первом вызове функции DoTheCount появляется MessageBox, в котором показан идентификатор потока, по которому мы можем опре делить поток, в котором выполняется код. Вместо Visual Basic 6.3 коман ды MessageBox используется MessageBox API, потому что функция API, как известно, поддерживает безопасное выполнение потоков. Второй MessageBox появляется после того, как закончен подсчет и сгенерирова но событие, которое указывает, что операция закончена. Фоновый поток запускается при помощи следующего кода в фор ме frmMTDemo3: Private Sub cmdCreateFree_Click() Set c = New clsBackground StartBackgroundThreadFree c End Sub Функция StartBackgroundThreadFree определена в модуле modMT Back следующим образом: Declare Function CreateThread Lib "kernel32" (ByVal lpSecurityAttributes _ As Long, ByVal dwStackSize As Long, ByVal lpStartAddress As Long, _ ByVal lpParameter As Long, ByVal dwCreationFlags As Long, _ lpThreadId As Long) As Long Declare Function CloseHandle Lib «kernel32» (ByVal hObject As Long) As Long ' Start the background thread for this object ' using the invalid free threading approach. Public Function StartBackgroundThreadFree(ByVal qobj As clsBackground) Dim threadid As Long Dim hnd& Dim threadparam As Long ' Free threaded approach threadparam = ObjPtr(qobj) hnd = CreateThread(0, 2003, AddressOf BackgroundFuncFree, _ threadparam, 0, threadid) If hnd = 0 Then ' Return with zero (error) Exit Function End If ' We don't need the thread handle CloseHandle hnd StartBackgroundThreadFree = threadid End Function Функция CreateThread имеет шесть параметров: ◆ lpSecurityAttributes — обычно устанавливается в нуль, чтобы использовать заданные по умолчанию атрибуты защиты. ◆ dwStackSize — размер стека. У каждого потока есть собственный стек. ◆ lpStartAddress — адрес памяти, где стартует поток. Он должен быть равен адресу функции в стандартном модуле, полученном при использовании оператора AddressOf. ◆ lpParameter — long 32 разрядный параметр, который передается функции, запускающей новый поток. ◆ dwCreationFlags — 32 бит переменная флагов, которая позволяет вам управлять запуском потока (активный, приостановленный и т. д.). Использование редактора Visual Basic 339 340 Использование редактора Visual Basic ◆ lpThreadId — переменная, в которую загружается уникальный идентификатором нового потока. Функция возвращает дескриптор потока. В этом случае мы передаем указатель на объект clsBackground, ко торый мы будем использовать в новом потоке. ObjPtr восстанавливает значение указателя интерфейса в переменную qobj. После создания по тока закрывается дескриптор при помощи функции CloseHandle. Это действие не завершает поток, — поток продолжает выполняться до вы хода из функции BackgroundFuncFree. Однако если мы не закрыли дес криптор, то объект потока будет существовать даже после выхода из функции BackgroundFuncFree. Все дескрипторы потока должны быть за крыты и при завершении потока система освобождает занятые потоком ресурсы. Функция BackgroundFuncFree имеет следующий код: ' A free threaded callback. ' A free threaded callback. ' This is an invalid approach, though it works ' in this case. Public Function BackgroundFuncFree(ByVal param As IUnknown) As Long Dim qobj As clsBackground Dim res& ' Free threaded approach Set qobj = param Do While Not qobj.DoTheCount(100000) Loop ' qobj.ShowAForm ' Crashes! ' Thread ends on return End Function Параметром этой функции является указатель на интерфейс ( ByVal param As IUnknown). При этом мы можем избежать неприятнос тей, потому что под COM каждый интерфейс основывается на IUnknown, так что такой тип параметра допустим независимо от типа интерфейса, передаваемого функции. Мы, однако, должны немедленно определить param как тип объекта, чтобы затем его использовать. В этом случае qobj устанавливается как объект clsBackground, который был передан к объек ту StartBackgroundThreadFree. Функция затем выполняет бесконечный цикл, в течение которого может выполняться любая требуемая операция, в этом случае использу ется повторный счет. Подобный подход мог бы использоваться здесь, чтобы выполнить операцию ожидания, которая приостанавливает поток пока не произойдет системное событие (типа завершения процесса). По ток затем мог бы вызвать метод класса, чтобы сообщить приложению, что событие произошло. Доступ к объекту qobj чрезвычайно быстр из за использования подхода свободного потока ( free threading) — никакая переадресация ( marshalling) при этом не используется. Обратите внимание на то, что если вы попробуете использовать объект clsBackground, который показывает форму, то это приведет к сбо ям приложения. Обратите также внимание на то, что событие заверше ния никогда не происходит в клиентской форме. Действительно, даже Microsoft Systems Journal, который описы вает этот подход, содержит очень много предупреждений о том, что при использовании этого подхода есть некоторые вещи, которые не работают. Является ли это дефектом в Visual Basic? Некоторые разработчики, кто пробовали развертывать приложе ния, применяющие этот тип многопоточности, обнаружили, что их при ложения вызывают сбои после обновления. Означает ли эта проблема, что Microsoft не сумел правильно обеспечить обратную совместимость? Ответ на оба вопроса: Нет Проблема не в Microsoft или Visual Basic. Вышеупомянутый код является мусором. Проблема проста — Visual Basic поддерживает объекты и в модели одиночного потока и в apartment model. Что это означает? Поведение объекта подчиненно изменениям, так как Visual Basic постоянно модифицируется. Это означает, что любая попытка объекта обратиться к другим объектам или формам может потерпеть неудачу и что причины отказов могут изменяться, поскольку эти объекты модифицируются. Даже код, который сейчас работает, может внезапно вызвать сбой, поскольку другие объекты добавляются, удаляются или изменяются. Это означает, что невозможно характеризовать поведение прило жения или предсказать, будет ли оно работать или может ли работать в любой данной среде. Невозможно предсказать, будет ли код работать на любой данной системе, и что его поведение может зависеть от используемой операци Использование редактора Visual Basic 341 342 Использование редактора Visual Basic онной, числа используемых процессоров и других проблем конфигура ции системы. Вы видите, что как только вы нарушаете соглашение COM, вы больше не защищены теми возможностями COM, которые позволяют объектам успешно взаимодействовать друг с другом и с клиентами. Этот подход является программной алхимией. Это безответствен но и ни один программист не должен поступать таким образом. Обратно к функции API CreateThread Теперь, когда стало понятно, почему подход к использованию CreateThread API является мусором, покажем, как можно использовать эту API функцию безопасно. Прием прост — вы должны просто твердо придерживаться согла шения COM о потоках. Это займет немного больше времени и усилий, но практика показала, что получаются очень надежные результаты. Пример MTDEMO3 демонстрирует этот подход в форме frm MTDemo3, имеющей код, который запускает класс фона в apartment model следующим образом: Private Sub cmdCreateApt_Click() Set c = New clsBackground StartBackgroundThreadApt c End Sub Пока этот процесс очень похож на подход свободных потоков. Вы создаете экземпляр класса и передаете его функции, которая запускает фоновый поток. В модуле modMTBack появляется следующий код: ' Structure to hold IDispatch GUID Type GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(7) As Byte End Type Public IID_IDispatch As GUID Declare Function CoMarshalInterThreadInterfaceInStream Lib "ole32.dll" _ (riid As GUID, ByVal pUnk As IUnknown, ppStm As Long) As Long Declare Function CoGetInterfaceAndReleaseStream Lib "ole32.dll" _ (ByVal pStm As Long, riid As GUID, pUnk As IUnknown) As Long Declare Function CoInitialize Lib "ole32.dll" (ByVal pvReserved As Long) As Long Declare Sub CoUninitialize Lib "ole32.dll" () ' Start the background thread for this object ' using the apartment model ' Returns zero on error Public Function StartBackgroundThreadApt(ByVal qobj As clsBackground) Dim threadid As Long Dim hnd&, res& Dim threadparam As Long Dim tobj As Object Set tobj = qobj ' Proper marshaled approach InitializeIID res = CoMarshalInterThreadInterfaceInStream(IID_IDispatch, qobj, threadparam) If res <> 0 Then StartBackgroundThreadApt = 0 Exit Function End If hnd = CreateThread(0, 2003, AddressOf BackgroundFuncApt, thread param, 0, threadid) If hnd = 0 Then ' Return with zero (error) Exit Function End If ' We don't need the thread handle CloseHandle hnd StartBackgroundThreadApt = threadid End Function Функция StartBackgroundThreadApt немного более сложна, чем ее эквивалент при применении подхода свободных потоков. Первая новая функция называется InitializeIID. Ее код таков: ' Initialize the GUID structure Private Sub InitializeIID() Static Initialized As Boolean If Initialized Then Exit Sub With IID_IDispatch .Data1 = &H20400 .Data2 = 0 .Data3 = 0 .Data4(0) = &HC0 .Data4(7) = &H46 End With Использование редактора Visual Basic 343 344 Использование редактора Visual Basic Initialized = True End Sub Вы видите, нам необходим идентификатор интерфейса — 16 бай товая структура, которая уникально определяет интерфейс. В частности, нам нужен идентификатор для интерфейса IDispatch. Функция InitializeIID инициализирует структуру IID_IDISPATCH к корректным значениям для идентификатора интерфейса IDispatch. Это значение по лучается с помощью использования утилиты просмотра системного рее стра. Зачем нам этот идентификатор? Чтобы твердо придерживаться со глашения COM о потоках, мы должны создать промежуточный объект (proxy object) для объекта clsBackground. Промежуточный объект должен быть передан новому потоку вместо первоначального объекта. Обраще ния к новому потоку на промежуточном объекте будут переадресованы ( marshaled) в текущий поток. CoMarshalInterThreadInterfaceInStream выполняет интересную за дачу. Она собирает всю информацию, необходимую при создании про межуточного объекта, для определенного интерфейса и загружает ее в объект потока ( stream object). В этом примере мы используем интерфейс IDispatch, потому что мы знаем, что каждый класс Visual Basic поддержи вает IDispatch и мы знаем, что поддержка переадресации (marshalling) IDispatch встроена в Windows — так что этот код будет работать всегда. Затем мы передаем объект потока ( stream object) новому потоку. Этот объект разработан Windows для обеспечения одинаковой передачи меж ду потоками, так что мы можем безопасно передавать его функции CreateThread. Остальная часть функции StartBackgroundThreadApt иден тична функции StartBackgroundThreadFree. Функция BackgroundFuncApt также сложнее, чем ее эквивалент при использовании модели свободных потоков и показана ниже: ' A correctly marshaled apartment model callback. ' This is the correct approach, though slower. Public Function BackgroundFuncApt(ByVal param As Long) As Long Dim qobj As Object Dim qobj2 As clsBackground Dim res& ' This new thread is a new apartment, we must ' initialize OLE for this apartment (VB doesn't seem to do it) res = CoInitialize(0) ' Proper apartment modeled approach res = CoGetInterfaceAndReleaseStream(param, IID_IDispatch, qobj) Set qobj2 = qobj Do While Not qobj2.DoTheCount(10000) Loop qobj2.ShowAForm ' Alternatively, you can put a wait function here, ' then call the qobj function when the wait is satisfied ' All calls to CoInitialize must be balanced CoUninitialize End Function Первый шаг должен инициализировать подсистему OLE для но вого потока. Это необходимо для переадресации ( marshalling) кода, что бы работать корректно. CoGetInterfaceAndReleaseStream создает проме жуточный объект для объекта clsBackground и реализует объект потока ( stream object), используемый для передачи данных из другого потока. Интерфейс IDispatch для нового объекта загружается в переменную qobj. Теперь возможно получить другие интерфейсы — промежуточный объ ект будет корректно переадресовывать данные для каждого интерфейса, который может поддерживать. Теперь вы можете видеть, почему цикл помещен в эту функцию вместо того, чтобы находиться непосредственно в объекте. Когда вы впервые вызовите функцию qobj2.DoTheCount, то увидите, что код вы полняется в начальном потоке. Каждый раз, кликая на метод объекта, вы фактически вызываете метод промежуточного объекта. Текущий поток приостанавливается, запрос метода переадресовывается первоначально му потоку и вызывается метод первоначального объекта в той же самом потоке, который создал объект. В случае, если бы цикл был в объекте, то вы бы заморозили первоначальный поток. Хорошим результатом применения этого подхода является то, что все работает правильно. Объект clsBackground может безопасно показы вать формы и генерировать события. Недостатком этого подхода являет ся, конечно, его более медленное исполнение. Переключение потоков и переадресация ( marshalling) — относительно медленные операции. Вы фактически никогда не захотите выполнять фоновую операцию как по казано здесь. Но этот подход может чрезвычайно хорошо работать, если вы мо жете помещать фоновую операцию непосредственно в функцию |