Такая модель относительно проста для реализации. Если Вы устраняете глобальные переменные (как делает Visual Basic), модель Apartment Threading автоматически гарантирует безопасность потока - так как каждый объект действительно выполняется в собственном потоке, и благодаря отсутствию глобальных переменных, объекты в разных потоках не взаимодействуют друг с другом.
Модель свободных потоков
Модель свободных потоков (Free Threading Model) заключается в следующем.. Любой объект может быть создан в любом потоке. Все методы и свойства любого объекта могут быть вызываны в любое время из любого потока. Объект принимает на себя всю ответственность за обработку любой необходимой синхронизации.
Это самая трудная в реализации модель, так как требуется, чтобы всю синхронизацию обрабатывал программист. Фактически до недавнего времени, технология OLE непосредственно не поддерживала эту модель! Однако, с тех пор marshalling никогда не требуется и это наиболее эффективная модель потоков.
Какую модель поддерживает ваш сервер?
Как приложение или сама Windows узнает, которую модель потоков использует сервер? Эта информация включена в системный реестр (registry). Когда Visual Basic создает объект, он проверяет системный реестр, чтобы определить, в каких случаях требуется использовать промежуточный объект (proxy object) и в каких - marshalling.
Эта проверка является обязанностью клиента и необходима для строгой поддержки требований многопоточности для каждого объекта, которого он создает.
Функция API CreateThread
Теперь давайте посмотрим, как с Visual Basic может использоваться функция API CreateThread. Скажем, Вы имеете класс, что Вы хотите выполненять в другом потоке, например, чтобы выполнить некоторую фоновую операцию. Характерный класс такого типа мог бы иметь следующий код (из примера MTDemo 3):
' Class clsBackground
' MTDemo 3 - Multithreading example
' Copyright © 1997 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
EndFunction
Класс разработан так, чтобы функция DoTheCount могла неоднократно вызываться из непрерывного цикла в фоновом потоке. Мы могли бы поместить цикл непосредственно в сам объект, но вы вскоре увидите, что были веские причины для проектирования объекта как показано в примере. При первом вызове функции DoTheCount появляется MessageBox, в котором показан идентификатор потока, по которому мы можем определить поток, в котором выполняется код. Вместо VB команды MessageBox используется MessageBox API, потому что функция API, как известно, поддерживает безопасное выполнение потоков. Второй MessageBox появляется после того, как закончен подсчет и сгенерировано событие, которое указывает, что операция закончена.
Фоновый поток запускается при помощи следующего кода в форме frmMTDemo3: Private Sub cmdCreateFree_Click()
Set c = New clsBackground
StartBackgroundThreadFree c
End Sub
Функция StartBackgroundThreadFree определена в модуле modMTBack следующим образом:
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, 2000, 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 бит переменная флагов, которая позволяет Вам управлять запуском потока (активный, приостановленный и т.д.). Подробнее об этих флагах можно почитать в Microsoft's online 32 bit reference.
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
EndFunction
Параметром этой функции является- указатель на интерфейс (ByVal param As IUnknown). При этом мы можем избежать неприятностей, потому что под COM каждый интерфейс основывается на IUnknown, так что такой тип параметра допустим независимо от типа интерфейса, передаваемого функции. Мы, однако, должны немедленно определить param как тип объекта, чтобы затем его использовать. В этом случае qobj установливается как объект clsBackground, который был передан к объекту StartBackgroundThreadFree.
Функция затем выполняет бесконечный цикл, в течение которого может выполняться любая требуемая операция, в этом случае повторный счет. Подобный подход мог бы использоваться здесь, чтобы выполнить операцию ожидания, которая приостанавливает поток пока не произойдкт системное событие (типа завершения процесса). Поток затем мог бы вызвать метод класса, чтобы сообщить приложению, что событие произошло.
Доступ к объекту qobj чрезвычайно быстр из-за использования подхода свободного потока (free threading) - никакая переадресация (marshalling) при этом не используется.
Обратите внимание на то, что если Вы попробуете использовать объект clsBackground, который показывает форму, то это приведет к сбоям приложения. Обратите также внимание на то, что событие завершения никогда не происходит в клиентской форме. Действительно, даже Microsoft Systems Journal, который описывает этот подход, содержит очень много предупреждений о том, что при использовании этого подхода есть некоторые вещи, которые не работают.
Некоторые разработчики, кто пробовали развертывать приложения, применяющие этот тип многопоточности, обнаружили, что их приложения вызывают сбои после обновления к VB5 service pack 2.
Является ли это дефектом Visual Basic?
Означает ли это, что Microsoft не обеспечила совместимость?
Ответ на оба вопроса: Нет
Проблема не в Microsoft или Visual Basic.
Проблема состоит в том, что вышеупомянутый код является мусором.
Проблема проста - Visual Basic поддерживает объекты и в модели одиночного потока и в apartment model. Позвольте мне перефразировать это: объекты Visual Basic являются COM объектами и они,согласно COM соглашению, будут правильно работать как в модели одиночного потока так и в apartment model. Это означает, что каждый объект ожидает, что любые вызовы методов будут происходить в том же самом потоке, который создал объект.
Пример, показанный выше, нарушает это правило.
Это нарушает соглашение COM.
Что это означает?
Это означает, что поведение объекта подчиненно изменениям, так как Visual Basic постоянно модифицируется.
Это означает, что любая попытка объекта обратиться к другим объектам или формам может потерпеть неудачу и что причины отказов могут изменяться, поскольку эти объекты модифицируются.
Это означает, что даже код, который сейчас работает, может внезапно вызвыть сбой, поскольку другие объекты добавляются, удаляются или изменяются.
Это означает, что невозможно характеризовать поведение приложения или предсказать, будет ли оно работать или может ли работать в любой данной среде.
Это означает, что невозможно предсказать, будет ли код работать на любой данной системе, и что его поведение может зависеть от используемой операционной, числа используемых процессоров и других проблем конфигурации системы.
Вы видите, что как только Вы нарушаете соглашение COM, Вы больше не защищены теми возможностями COM, которые позволяют объектам успешно взаимодействовать друг с другом и с клиентами.
Этот подход является программной алхимией. Это безответственно и ни один программист не должен когда-либо использовать это. Точка.
Обратно к функции API CreateThread
Теперь, когда я показал Вам, почему подход к использованию CreateThread API, показанный в некоторых статьях, является мусором, я покажу Вам, как можно использовать эту API функцию безопасно. Прием прост - Вы должны просто твердо придержаться соглашения COM о потоках. Это займет немного больше времени и усилий, но практика показала, что получаются очень надежные результаты.
Пример MTDEMO3 демонстрирует этот подход в форме frmMTDemo3, имеющей код, который запускает класс фона в apartment model следующим образом:
Private Sub cmdCreateApt_Click()
Set c = New clsBackground