Реферат: Потоки в Visual Basic

Споявлением оператора AddressOf, часть индустрии ПО стала ориентироваться наавторов, показывающих  как с использованием Visual Basic решать ранееневозможные задачи. Другая часть быстро охватила консультантов, помогающихпользователям, имеющим проблемы при решении таких задач.

Проблемане в Visual Basic или в технологии. Проблема в том, что большинство авторовприменяют одно и  тоже правило к AddressOf методикам, что большинство компанийпо разработке ПО считают, что если Вы  должны что-то сделать, то Вы сможете.Идея о том, что применение самой новой и последней технологии  должно, поопределению, быть самым лучшим решением проблемы, широко распространена виндустрии ПО.

Этаидея неверна. Развертывание технологии должно управляться прежде всегопроблемой, которую  необходимо решить решить, а не технологией, которую кто-топробует Вам впарить;).  

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

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

Недавниестатьи в Microsoft Systems Journal и Visual Basic Programmer's Journalпредставили программистам на  Visual Basic возможность использования функцииAPI CreateThread, чтобы непосредственно поддерживать  многопоточный режим подVisual Basic. После этого, один читатель пожаловался, что моя книга VisualBasic  Programmer's Guide to the Win32 API является неполной, потому что я неописал в ней эту функцию и не  продемонстрировал эту технологию. Эта статья — частично является ответом этому читателю, и частично —  ответом на другиестатьи, написанными на эту тему. Эта статья также является дополнением к главе14 моей  книги «Разработка ActiveX компонент на Visual Basic 5.0»относительно новых возможностей, обеспечиваемых  Visual Basic 5.0 Service Pack2.  

Быстрый обзор Многопоточности 

ЕслиВы уже хорошо разбираетесь в технологии многопоточного режима, то Вы можетепропустить этот  раздел и продолжать чтение с раздела, названного «Чтонового в Service Pack 2.»  

Каждый,кто использует Windows, знает, что Windows способно делать больше чем одну вещьодновременно.  

Можетодновременно выполнять несколько программ, при одновременном проигрываниикомпакт-диска,  посылке факса и пересылке файлов. Каждый программист знает (илидолжен знать) что ЦЕНТРАЛЬНЫЙ  ПРОЦЕССОР компьютера может только выполнять однукоманду одновременно (проигнорируем  существование многопроцессорных машин).Как единственный ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР может  выполнять множество задач?  

Этоделается быстрым переключением между многими задачами. Операционная системасодержит в памяти  все программы, которые запущены в настоящий момент. Это позволяетЦЕНТРАЛЬНОМУ ПРОЦЕССОРУ  выполнять программы по очереди. Каждый раз происходитпереключение между программами, при этом  меняется содержимое внутреннихрегистров, включая указатель команды и указатель вершины стека. Каждая из  таких«задач» называется потоком выполнения (thread of execution).  

Впростой многозадачной системе, каждая программа имеет емеет единственный поток.Это означает, что  ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР начинает выполнение команд в началепрограммы и продолжает следуя  инструкциям в последовательности, определеннойпрограммой до тех пор, пока программа не завершается.  

Скажем,программа имеет пять команд: B C D и E, которые выполняются последовательно(никаких  переходов нет в этом примере). Когда приложение имеет один поток,команды будут всегда выполнять в точно  том же самом порядке: A, B, C, D и E.Действительно, ЦЕНТРАЛЬНЫЙ ПРОЦЕССОР может потребовать  времени для выполнениядругих команд в других программах, но они не будут влиять на это приложение,если  не имеется конфликт над общими ресурсами системы, но это уже отдельнаятема для разговора.  

Продвинутаямногопоточная операционная система типа Windows позволяет приложению выполнять  большечем один поток одновременно. Скажем, команда D в нашем типовом приложении могласоздать новый  поток, который стартовал командой B и далее выполнялпоследовательность команд C и E. Первый поток был  бы все еще A, B, C, D, E, нокогда команда D выполнится, возникнет новый поток, который выполнит команды  быB, C, E (здесь команды D уже не будет, иначе мы получим еще один поток).

Вкаком порядке будут следовать команды в этом приложении?

Этомогло бы быть:

Thread1 A B C D E E

Thread2 B C

Илитак:  

Thread 1 A B C D E  

Thread 2 B C E  

Илиэтак: 

Thread 1 A B C D E 

Thread2 B C E   

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

Почему- это проблема?  

Имитатор Многопоточности

Рассмотримпроект MTDemo:

Проектсодержит один модуль кода, в котором содержится две глобальных переменных:

' MTDemo — Multithreading Demo program

' Copyright © 1997 by Desaware Inc. All Rights Reserved

Option Explicit

Public GenericGlobalCounter As Long

PublicTotalIncrements As Long

'Этот проект содержит одну форму — frmMTDemo1, которая содержит

' следующий код:

' MTDemo — Multithreading Demo program

' Copyright © 1997 by Desaware Inc. All Rights Reserved

Option Explicit

Dim State As Integer 

' State = 0 — Idle 

' State = 1 — Loading existing value 

' State = 2 — Adding 1 to existing value 

' State = 3 — Storing existing value 

' State = 4 — Extra delay

Dim Accumulator As Long

Const OtherCodeDelay = 10

Private Sub Command1_Click() 

Dim f As New frmMTDemo1 

f.Show

End Sub

Private Sub Form_Load() 

Timer1.Interval = 750 + Rnd * 500

End Sub

Private Sub Timer1_Timer() 

Static otherdelay& 

Select Case State 

Case 0 

lblOperation = «Idle» 

State = 1 

Case 1 

lblOperation = «Loading Acc» 

Accumulator = GenericGlobalCounter 

State = 2 

Case 2 

lblOperation = «Incrementing» 

Accumulator = Accumulator + 1 

State = 3 

Case 3 

lblOperation = «Storing» 

GenericGlobalCounter = Accumulator 

TotalIncrements = TotalIncrements + 1 

State = 4 

Case 4 

lblOperation = «Generic Code» 

If otherdelay >= OtherCodeDelay Then 

State = 0 

otherdelay = 0 

Else 

otherdelay = otherdelay + 1 

End If 

End Select 

UpdateDisplay

End Sub

Public Sub UpdateDisplay() 

lblGlobalCounter = Str$(GenericGlobalCounter) 

lblAccumulator = Str$(Accumulator) 

lblVerification = Str$(TotalIncrements)

EndSub  

Этапрограмма для моделирования многопоточного режима использует таймер и простойконечный  автомат. Переменная State описывает пять команд, которые этапрограмма выполняет. State = 0 — неактивное  состояние. State = 1 загружаетлокальную переменную глобальной переменной GenericGlobalCounter. State = 2  увеличиваетна единицу локальную переменную. State = 3 запоминает результат в переменной  GenericGlobalCounterи увеличивает переменную TotalIncrements (которая считает количество приращений переменной GenericGlobalCounter). State = 3 добавляет дополнительную задержку,представляющую собой  время, затраченное на выполнение других команд впрограмме.  

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

Каждыйсигнал таймера моделирует цикл ЦЕНТРАЛЬНОГО ПРОЦЕССОРА в текущем потоке. ЕслиВы  запустите программу, то увидете, что значение переменнойGenericGlobalCounter будет всегда точно равно  переменной TotalIncrements,потому что переменная TotalIncrements показывает количество увеличений счетчика GenericGlobalCounter потоком.  

Ночто случится, когда Вы нажимаете кнопку Command1 и запустите второй экземплярформы? Эта новая  форма смоделирует второй поток.  

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

Другимисловами, переменная была увеличена дважды, но значение увеличилось только наединицу. Если Вы  запускаете несколько форм, то сразу заметите, что числоприращений, представляемой переменной  TotalIncrements, растет намного быстрее,чем счетчик GenericGlobalCounter.  

Что,если переменная представляет объектный счет блокировки — который следит, когдаобъект должен быть  освобожден? Что, если она представляет собой сигнал,который указывает, что ресурс находится в  использовании?  

Такаяпроблема может привести к появлению ресурсов, постоянно недоступных в системе,к объекту,  блокируемому в памяти, или преждевременно освобожденному. Это можетпривести к сбоям приложения.  

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

Решение проблем Многопоточности

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

Избегайтевсеобщего использования глобальных переменных.

Добавьтекод синхронизации везде, где используются глобальные переменные.  

Первыйподход используется в основном в Visual Basic. Когда Вы включаете многопоточныйрежим в Visual

Basicприложения, все глобальные переменные станут локальными для специфическогопотока. Это  свойственно способу, с которым Visual Basic выполняет apartmentmodel threading — подробнее об этом позднее.  

Первоначальныйвыпуск Visual Basic 5.0 позволял использовать многопоточность только в компонентах, которые не имели никаких элементов пользовательского интерфейса. Так былопотому что они не имели  безопасного потока управления формами. Например: когдаВы создаете форму в Visual Basic, VB дает ей имя  глобальной переменной (такимобразом, если Вы имеете форму, именованную Form1, Вы можете  непосредственнообращаться к ее методам, используя Form1.метод вместо того, чтобы объявитьотдельную  переменную формы). Этот тип глобальной переменной может вызыватьпроблемы многопоточного режима,  которые Вы видели ранее. Имелись несомненнодругие проблемы внутри управления формами.  

Сservice pack 2, управление формами Visual Basic было сделано безопаснымпотоком. Это говорит о том, что  каждый поток имеет собственную глобальнуюпеременную для каждой формы, определенной в проекте.  

Что нового в Service Pack 2 

Сделавпоток управления формами безопасным, Service pack 2 предоставил возможность спомощью Visual

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

Приложениедолжно быть определено как программа ActiveX Exe с установкой запуска из SubMain:

' MTDemo2 — Multithreading demo program

' Copyright © 1997 by Desaware Inc. All Rights Reserved

Option Explicit

Declare Function FindWindow Lib «user32» Alias«FindWindowA» _

(ByVal lpClassName As String, ByVal lpWindowName As String) _ 

As Long

Sub Main() 

Dim f As frmMTDemo2 

' We need this because Main is called on each new thread 

Dim hwnd As Long 

hwnd = FindWindow(vbNullString, «Multithreading Demo2») 

If hwnd = 0 Then 

Set f = New frmMTDemo2 

f.Show 

Set f = Nothing 

End If

EndSub  

Первыйраз программа загружает и отображает основную форму приложения. ПодпрограммаMain должна  выяснить, является ли это первым потоком приложения, поэтому этоткод выполняется при старте каждого  потока. Вы не можете использоватьглобальную переменную, чтобы это выяснить, потому что Visual Basic  apartmentmodel хранит глобальные переменные специфическими для одиночного потока. В этомпримере  используется функция API FindWindow, чтобы проверить, была лизагружена основная форма примера.

Имеютсядругие способы выяснить, является ли это основным потоком, включаяиспользование объектов  синхронизации системы — но это отдельная тема дляразговора.  

Многопоточныйрежим реализуется созданием объекта в новом потоке. Объект должен бытьопределен,  используя модуль класса. В этом случае, простой модуль классаопределяется следующим образом:

' MTDemo2 — Multithreading demo program

' Copyright © 1997 by Desaware Inc. All Rights Reserved

Option Explicit

Private Sub Class_Initialize() 

Dim f As New frmMTDemo2 

f.Show 

Setf = Nothing

EndSub  

Мыможем установить переменную формы как nothing после того, как она создана,потому что после  отображения формы она будет сохранена.

' MTDemo2 — Multithreading demo program

' Copyright © 1997 by Desaware Inc. All Rights Reserved

Option Explicit

Private Sub cmdLaunch1_Click() 

Dim c As New clsMTDemo2 

c.DisplayObjPtr Nothing

End Sub

Private Sub cmdLaunch2_Click() 

Dim c As clsMTDemo2 

Set c = CreateObject(«MTDemo2.clsMTDemo2»)

End Sub

Private Sub Form_Load() 

lblThread.Caption = Str$(App.ThreadID)

EndSub  

Формаотображает идентификатор потока в метке на форме. Форма содержит две командныекнопки, одна из  которых использует оператор New, другая -использует операторCreateObject.  

ЕслиВы запустите программу внутри среды Visual Basic, то увидите, что формы всегдасоздаются в одном и  том же потоке. Это происходит, потому что среда VisualBasic поддерживает только одиночный поток. Если Вы  скомпилируете и запуститепрограмму, то увидите, что подход, использующий CreateObject создает и  clsMTDemo2и ее форму в новом потоке.  

Почему многопоточность 

Откудався суета относительно многопоточного режима, если он включает так много потенциальной опасности? Потому что, в некоторых ситуациях, многопоточный режим можетзначительно улучшать  эффективность приложения. В некоторых случаях это можетулучшать эффективность некоторых операций  синхронизации типа ожиданиязавершения приложения. Это позволяет сделать архитектуру приложения более  гибкой.Например, операция Add a long в форме MTDEMO2 со следующим кодом:

Private Sub cmdLongOp_Click() 

Dim l& 

Dim s$ 

For l = 1 To 1000000 

s = Chr$(l And &H7F) 

Nextl

EndSub  

Запуститенесколько экземпляров формы, используя кнопку cmdLaunch1. Когда Вы нажимаете накнопку  cmdLongOp на любой из форм, то увидите, что это действие замораживаетоперации на всех других формах. Так  происходит, потому что все формывыполняются в одиночном потоке — и этот поток занят выполнением  длинногоцикла. Если Вы запустите несколько экземпляров формы кнопкой cmdLaunch2 инажимете кнопку  cmdLongOp на форму, то только эта форма будет заморожена — другие формы будут активными. Они  выполняются в собственных потоках, и длинныйцикл будет выполняться только в собственном потоке.

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

Дальшекраткое резюме, когда важен многопоточный режим:

СерверActiveX EXE – без общих ресурсов.  

КогдаВы имеете ActiveX EXE сервер, который Вы собираетесь совместно использоватьсреди   нескольких приложений, многопоточный режим предотвращает приложения отнежелательных   взаимодействий с друг другом. Если одно приложение выполняет длиннуюоперацию на объекте в   однопоточном сервере, другие приложения будутвытеснены, т.е. будут ждать, когда освободится   сервер. Многопоточный режимрещает эту проблему. Однако, имеются случаи, где Вы можете   хотетьиспользовать ActiveX EXE сервер, чтобы регулировать доступ к общедоступнномуресурсу   (shared resource). Например, сервер stock quote, описанный в моейкниге Developing ActiveX   Components. В этом случае сервер stock quoteвыполняется в одиночном потоке и который доступен   для всех приложений,использующих сервер по очереди.

Многопоточный клиент – выполняемый как ActiveX EXEсервер 

Простаяформа этого подхода продемонстрирована в приложении MTDEMO2. Этот подход   используется,когда приложение поддерживает множественные окна, которые должны исходить из   одногоприложения, но работать полностью независимо. Интернет-браузер — хороший пример  такого многопоточного клиента, где каждое окно выполняется в собственномпотоке. Здесь следует   обратить внимание на то, что многопоточный режим недолжен использоваться как замена для   хорошего событийно управляемого проекта.

Многопоточные серверы DLL или EXE 

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

Соглашение о потоках 

Веритеили нет, но все это было введением. Часть этого материала является обзоромматериала, который  описан в моей книге Developing ActiveX Components, другаячасть материала описывает новую информацию для  service pack 2.  

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

Дано: 

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

Вопрос: 

Какэто возможно, что Visual Basic позволяет Вам создавать объекты и использоватьих с одиночными и  многопоточными средами безотносительно к тому, разработаныли они для одиночного или многопоточного  использования?  

Другимисловами — Как многопоточные Visual Basic приложения могут использовать объекты,которые не  разработаны для безопасного выполнения в многопоточной среде? Какмогут другие многопоточные  приложения использовать однопоточные объекты VisualBasic?

Коротко:как COM поддерживает потоки?

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

Большинстволюдей сначала думает о интерфейсной части соглашения — о методах и свойствах,которые  предоставляет объект.  

НоВы не можете не знать того, что COM также определяет поточность как частьсоглашения. И подобно  любой части соглашения COM — если Вы нарушаете этиусловия, то будете иметь проблемы. Visual Basic,  естественно, скрывает от Васбольшинство механизмов COM, но чтобы понять как использовать  многопоточность вVisual Basic, Вы должны разобраться COM модели потоков.

Модельодиночного потока:  

Однопоточныйсервер — самый простой тип реализации сервера. И самый простой для понимания.  

Вэтом случае EXE сервер выполняется в одиночном потоке. Все объекты создаются вэтом потоке.  

Всевызовы методов каждого объекта, поддерживаемого сервером должны прибыть в этотпоток.  

Ночто будет, если клиент выполняется в другом потоке? В том случае, для объектасервера должен   быть создан промежуточный объект (proxy object). Этотпромежуточный объект выполняется в   потоке клиента и отражает методы исвойства фактического объекта. Когда вызывается метод   промежуточного объекта,он выполняет операции, необходимые для подключению к потоку   объекта, а затемвызывает метод фактического объекта, используя параметры, переданные к   промежуточномуобъекту. Естественно, что этот подход требует значительного времени на   выполнениезадачи, однако он позволяет выполнить все соглашения. Этот процесс переключения  потоков и пересылки данных от промежуточного объекта к фактическому объекту иобратно   называется marshalling. Эта тема обсуждается в главе 6 моей книгиDeveloping ActiveX Components.  

Вслучае DLL серверов, одиночная потоковая модель требует, чтобы все объекты всервере   создавались и вызывались в том же самом потоке что и первый объект,созданный сервером.

Модель Apartment Threading 

Обратитевнимание, что модель Apartment Threading как определено COM не требует, чтобыкаждый   поток имел собственный набор глобальных переменных. Visual Basic такимобразом реализует   модель Apartment Threading. Модель Apartment Threadingдекларирует, что каждый объект может быть   создан в собственном потоке,однако, как только объект создан, его методы и свойства могут   вызыватьсятолько тем же самым потоком, которая создал объект. Если объект другого потока   захочетиметь доступ к методам этого объекта, то он должен действовать черезпромежуточный   объект.  

Такаямодель относительно проста для реализации. Если Вы устраняете глобальныепеременные   (как делает Visual Basic), модель Apartment Threadingавтоматически гарантирует безопасность потока   - так как каждый объектдействительно выполняется в собственном потоке, и благодаря отсутствию   глобальныхпеременных, объекты в разных потоках не взаимодействуют друг с другом.

Модель свободных потоков 

Модельсвободных потоков (Free Threading Model) заключается в следующем… Любой объектможет   быть создан в любом потоке. Все методы и свойства любого объекта могутбыть вызываны в любое   время из любого потока. Объект принимает на себя всюответственность за обработку любой   необходимой синхронизации.  

Этосамая трудная в реализации модель, так как требуется, чтобы всю синхронизациюобрабатывал   программист. Фактически до недавнего времени, технология OLEнепосредственно не   поддерживала эту модель! Однако, с тех пор marshallingникогда не требуется и это наиболее   эффективная модель потоков.

Какуюмодель поддерживает ваш сервер?

Какприложение или сама Windows узнает, которую модель потоков использует сервер?Эта информация  включена в системный реестр (registry). Когда Visual Basicсоздает объект, он проверяет системный реестр,  чтобы определить, в какихслучаях требуется использовать промежуточный объект (proxy object) и в каких —  marshalling.

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

ФункцияAPI CreateThread  

Теперьдавайте посмотрим, как с Visual Basic может использоваться функция APICreateThread. Скажем, Вы  имеете класс, что Вы хотите выполненять в другомпотоке, например, чтобы выполнить некоторую фоновую  операцию. Характерныйкласс такого типа мог бы иметь следующий код (из примера 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

End Function 

Классразработан так, чтобы функция 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'sonline 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

End Function 

Параметромэтой функции является- указатель на интерфейс (ByVal param As IUnknown). Приэтом мы можем  избежать неприятностей, потому что под COM каждый интерфейсосновывается на IUnknown, так что такой  тип параметра допустим независимо оттипа интерфейса, передаваемого функции. Мы, однако, должны  немедленноопределить param как тип объекта, чтобы затем его использовать. В этом случаеqobj  установливается как объект clsBackground, который был передан к объектуStartBackgroundThreadFree.  

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

Доступк объекту qobj чрезвычайно быстр из-за использования подхода свободного потока(free threading) —  никакая переадресация (marshalling) при этом неиспользуется.  

Обратитевнимание на то, что если Вы попробуете использовать объект clsBackground,который показывает  форму, то это приведет к сбоям приложения. Обратите такжевнимание на то, что событие завершения никогда  не происходит в клиентскойформе. Действительно, даже Microsoft Systems Journal, который описывает этот  подход,содержит очень много предупреждений о том, что при использовании этого подходаесть некоторые  вещи, которые не работают.  

Некоторыеразработчики, кто пробовали развертывать приложения, применяющие этот тип  многопоточности,обнаружили, что их приложения вызывают сбои после обновления к VB5 service pack2.

Являетсяли это дефектом 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 

StartBackgroundThreadAptc

EndSub  

Покаэто выглядит очень похоже на подход свободных потоков. Вы создаете экземпляркласса и передаете  его функции, которая запускает фоновый поток. В модуле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, 2000, AddressOf _ 

BackgroundFuncApt, threadparam, 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 

Initialized= True

EndSub  

Вывидите, нам необходим идентификатор интерфейса — 16 байтовая структура, котораяуникально  определяет интерфейс. В частности нам необходим идентификаторинтерфейса для интерфейса IDispatch  (подробная информация относительноIDispatch может быть найдена в моей книге Developing ActiveX  Components).Функция 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

EndFunction  

Первыйшаг должен инициализировать подсистему OLE для нового потока. Это необходимодля  переадресации (marshalling) кода, чтобы работать корректно.CoGetInterfaceAndReleaseStream создает  промежуточный объект для объектаclsBackground и реализует объект потока (stream object), используемый для  передачиданных из другого потока. Интерфейс IDispatch для нового объекта загружается впеременную qobj.

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

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

Хорошимрезультатом применения этого подхода является то, что все работает правильно.Объект  clsBackground может безопасно показывать формы и генерировать события.Недостатком этого подхода  является, конечно, его более медленное исполнение.Переключение потоков и переадресация (marshalling) —  относительно медленныеоперации. Вы фактически никогда не захотите выполнять фоновую операцию как  показаноздесь.  

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

Что,если Вы хотите выполнить фоновую операцию, которая не должна использоватьобъект? Очевидно,  проблемы с соглашением COM о потоках исчезают. Но появляютсядругие проблемы. Как фоновый поток  сообщит о своем завершении приоритетномупотоку? Как они обмениваются данными? Как два потока будут  синхронизированы?Все это возможно выполнить с помощью соответствующих вызовов API. В моей книге  VisualBasic 5.0 Programmer's Guide to the Win32 API имеется информации относительнообъектов синхронизации  типа Событий, Mutexes, Семафоров и Waitable Таймеров.  

Этакнига также включает примеры файлов отображаемых в память, которые могут бытьполезны при  обмене данных между процессами. Вы сможете использовать глобальныепеременные, чтобы обмениваться  данные, но надо знать, что такое поведение негарантируется Visual Basic(другими словами, даже если это  сейчас работает, неимеется никаких гарантий, что это будет работать в будущем). В этом случае ямог бы  предложить Вам использовать для обмена данными методики, основанные наAPI. Однако, преимуществом  показанного здесь подхода, основанного на объектах,является то, что этот подход делает проблему обмена  данными между потокамитривиальной, просто делайте это через объект.  

Заключение 

Яоднажды услышал от опытного программиста под Windows, что OLE является самойтрудной технологией,  которой он когда-либо обучался. Я с этим согласен. Этоочень обширная тема, и некоторые части этой  технологии очень трудно понять.Visual Basic, как всегда, скрывает от Вас много сложностей.  

Имеетсясильное искушение, чтобы пользоваться преимуществом продвинутых методов типа  многопоточногорежима, используя подход «tips and techniques». Это искушениепоощрено некоторыми статьями,  которые иногда представляют специфическоерешение, приглашая Вас вырезать и вставить (cut and past) их  методики в вашисобственные приложения.  

Когдая писал книгу Visual Basic Programmer's Guide to the Windows API, я выступалпротив такого подхода к  программированию. Я чувствовал, что вообщебезответственно включать в приложение код, который Вы не  понимаете, и чтореальное знание, которое так тяжело получить, стоит затраченных усилий.  

Такимобразом мои книги по API были разработаны, чтобы обеспечить не быстрые ответы ипростые  решения, а чтобы обучить использованию API к такой степени, чтопрограммисты могли бы интеллектуально  правильно применять даже наиболеепродвинутые методы. Я применил это тот же самый подход к моей книге  DevelopingActiveX Components, которая требует много времени для обсуждения принциповActiveX, COM и  объектно-ориентированного программирования перед описаниемподробностей реализации этой технологии.  

Многоеиз моей карьеры на ниве Visual Basic и многое из деятельности в фирме Desaware,основано на  обучении Visual Basic программистов продвинутым методам. Читатель,кто вдохновил меня на написание этой  статьи, критикуя меня за сдерживаниетехнологии многопоточности, пропустил точку.  

Да,я обучаю и демонстрирую продвинутые методы программирования — но я пытаюсьникогда не  пропустить большую картинку. Продвинутые методы, которым я обучаю,должны быть непротиворечивы с  правилами и спецификациями Windows. Они должныбыть такими безопасными, насколько это возможно.

Онидолжны быть поддерживаемыми в конечном счете. Они не должны разрушаться, когдаизменяются  Windows или Visual Basic.  

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

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

Яне могу обещать, что использование apartment model версии CreateThread являетсяабсолютно корректным,  но мое понимание проблемы и опыт показывают, что этобезопасно.  

Могутиметься другие факторы, которые я пропустил. OLE — действительно сложная вещь имодули OLE DLL  и сам Visual Basic подвержены изменениям. Я только могуутверждать, что лучшее из моего знания — код,  который я здесь показал,удовлетворяет правилам COM и что эмпирическое доказательство показывает, что  VisualBasic runtime 5 0's является достаточно безопасным для выполнения фонового кодапотока в стандартном  модуле.

Список литературы

Дляподготовки данной работы были использованы материалы с сайта visualprogs.narod.ru/

еще рефераты
Еще работы по информатике, программированию