Потоки в Visual Basic

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

З появою оператора AddressOf, частина індустрії ПЗ стала орієнтуватися на авторів, що показують як з використанням Visual Basic вирішувати раніше неможливі завдання. Інша частина швидко охопила консультантів, які допомагають користувачам, що мають проблеми при вирішенні таких завдань.

Проблема не в Visual Basic або в технології. Проблема в тому, що більшість авторів застосовують одне і теж правило до AddressOf методиками, що більшість компаній з розробки ПЗ вважають, що якщо Ви повинні щось зробити, то Ви зможете. Ідея про те, що застосування найновішою і останньої технології має, за визначенням, бути найкращим вирішенням проблеми, широко поширена в індустрії ПЗ. Ця ідея невірна. Розгортання технології має управлятися перш за все проблемою, яку необхідно вирішити вирішити, а не технологією, яку хтось пробує Вам впарити;).

Дуже погано, що через те, що компанії часто нехтують згадкою про обмеження і недоліки їх інструментальних засобів, автори інколи бувають не в змозі звернути увагу читачів на наслідки деяких методик, які вони описують. І журнали і книги іноді нехтують своєю відповідальністю, щоб упевнитися, що практика програмування, яку вони описують, є прийнятною.

Програмісту дуже важливо вибрати необхідний інструмент для своєї роботи. Це - ваше завдання, щоб розробити код, який працює тепер не тільки на одній специфічній платформі, але також працює на різних платформах і системних конфігураціях. Ваш код повинен бути добре документований і підтриманий іншими програмістами, які беруть участь у проекті. Ваш код повинен слідувати правилам, продиктованими операційною системою або стандартами, які Ви використовуєте. Відмова так робити може призвести до проблем у майбутньому, оскільки системи та програмне забезпечення постійно удосконалюються.

Недавні статті в Microsoft Systems Journal і Visual Basic Programmer's Journal представили програмістам на Visual Basic можливість використання функції API CreateThread, щоб безпосередньо підтримувати багатопотоковий режим під Visual Basic. Після цього, один читач поскаржився, що моя книга Visual Basic Programmer's Guide to the Win32 API є неповною, тому що я не описав у ній цю функцію і не продемонстрував цю технологію. Ця стаття - частково є відповіддю цьому читачеві, і частково - відповіддю на інші статті, написаними на цю тему. Ця стаття також є доповненням до главі 14 моєї книги "Розробка ActiveX компонент на Visual Basic 5.0" щодо нових можливостей, забезпечуваних Visual Basic 5.0 Service Pack 2.

Швидкий огляд багатопоточності

Якщо Ви вже добре розбираєтеся в технології багатопотокового режиму, то Ви можете пропустити цей розділ і продовжувати читання з розділу, названого "Що нового в Service Pack 2."

Кожен, хто використовує Windows, знає, що Windows здатне робити більше ніж одну річ одночасно. Може одночасно виконувати декілька програм, при одночасному програванні компакт-диска, посилці факсу та пересилання файлів. Кожен програміст знає (або повинен знати) що центральний процесор комп'ютера може тільки виконувати одну команду одночасно (проігноруємо існування багатопроцесорних машин). Як єдиний центральний процесор може виконувати безліч завдань?

Це робиться швидким перемиканням між багатьма завданнями. Операційна система містить у пам'яті всі програми, які запущені зараз. Це дозволяє центральному процесору виконувати програми по черзі. Кожного разу відбувається перемикання між програмами, при цьому змінюється вміст внутрішніх регістрів, включаючи покажчик команди і покажчик вершини стека. Кожна з таких "завдань" називається потоком виконання (thread of execution).

У простій багатозадачного системі, кожна програма має емеет єдиний потік. Це означає, що центральний процесор починає виконання команд на початку програми і продовжує слідуючи інструкціям в послідовності, визначеною програмою до тих пір, поки програма не завершується.

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

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

У якому порядку будуть слідувати команди в цьому додатку?

Це могло б бути:

Thread 1 ABCDE

Thread 2 BCE

Або так:

Thread 1 ABCDE

Thread 2 BCE

Чи так:

Thread 1 ABCDE

Thread 2 BCE

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

Чому - це проблема?

Імітатор багатопоточності

Розглянемо проект MTDemo:

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

'MTDemo - Multithreading Demo program

'Copyright © 1997 by Desaware Inc. All Rights Reserved

Option Explicit

Public GenericGlobalCounter As Long

Public TotalIncrements 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)

End Sub

Ця програма для моделювання багатопотокового режиму використовує таймер і простий кінцевий автомат. Змінна 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 виконує apartment model 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 з установкою запуску з Sub Main:

'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

End Sub

Перший раз програма завантажує і відображає основну форму додатку. Підпрограма Main повинна з'ясувати, чи є це першим потоком додатки, тому цей код виконується при старті кожного потоку. Ви не можете використовувати глобальну змінну, щоб це з'ясувати, тому що Visual Basic apartment model зберігає глобальні змінні специфічними для одиночного потоку. У цьому прикладі використовується функція 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

Set f = Nothing

End Sub

Ми можемо встановити змінну форми як 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)

End Sub

Форма відображає ідентифікатор потоку в мітці на формі. Форма містить дві командні кнопки, одна з яких використовує оператор New, інша-використовує оператор CreateObject.

Якщо Ви запустіть програму всередині середовища Visual Basic, то побачите, що форми завжди створюються в одному і тому ж потоці. Це відбувається, тому що середовище Visual Basic підтримує тільки одиночний потік. Якщо Ви скомпілюєте і запустіть програму, то побачите, що підхід, який використовує CreateObject створює і clsMTDemo2 та її форму в новому потоці.

Чому багатопоточність

Звідки вся суєта щодо багатопотокового режиму, якщо він включає так багато потенційної небезпеки? Тому що, в деяких ситуаціях, багато-режим може значно покращувати ефективність програми. У деяких випадках це може покращувати ефективність деяких операцій синхронізації типу очікування завершення програми. Це дозволяє зробити архітектуру програми більш гнучкою. Наприклад, операція Add a long у формі MTDEMO2 з наступним кодом:

Private Sub cmdLongOp_Click ()

Dim l &

Dim s $

For l = 1 To 1000000

s = Chr $ (l And & H7F)

Next l

End Sub

Запустіть кілька примірників форми, використовуючи кнопку 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 додатки можуть використовувати об'єкти, які не розроблені для безпечного виконання в багатопотоковому середовищі? Як можуть інші багатопотокові додатка використовувати однопоточні об'єкти Visual Basic?

Коротко: як 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 може використовуватися функція 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

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'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

End Function

Параметром цієї функції є-покажчик на інтерфейс (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

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

End Sub

Ви бачите, нам необхідний ідентифікатор інтерфейсу - 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

End Function

Перший крок повинен ініціалізувати підсистему OLE для нового потоку. Це необхідно для переадресації (marshalling) коду, щоб працювати коректно. CoGetInterfaceAndReleaseStream створює проміжний об'єкт для об'єкта clsBackground і реалізує об'єкт потоку (stream object), використовуваний для передачі даних з іншого потоку. Інтерфейс IDispatch для нового об'єкта завантажується в змінну qobj. Тепер можливо отримати інші інтерфейси - проміжний об'єкт буде коректно переадресовувати дані для кожного інтерфейсу, який може підтримувати.

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

Хорошим результатом застосування цього підходу є те, що все працює правильно. Об'єкт clsBackground може безпечно показувати форми і генерувати події. Недоліком цього підходу є, звичайно, його більш повільне виконання. Перемикання потоків і переадресація (marshalling) - відносно повільні операції. Ви фактично ніколи не захочете виконувати фонову операцію як показано тут.

Але цей підхід може надзвичайно добре працювати, якщо Ви можете поміщати фонову операцію безпосередньо в функцію BackgroundFuncApt! Наприклад: Ви могли б мати фоновий потік, що виконує фонові обчислення або операцію очікування системи. Коли вони будуть завершені, ви можете викликати метод об'єкта, який згенерує подія в клієнті. Зберігаючи кількість викликів методу, невелике щодо кількості роботи, що виконується в фонової функції, Ви можете досягати дуже ефективних результатів.

Що, якщо Ви хочете виконати фонову операцію, яка не повинна використовувати об'єкт? Очевидно, проблеми з угодою COM про потоки зникають. Але з'являються інші проблеми. Як фоновий потік повідомить про своє завершення пріоритетного потоку? Як вони обмінюються даними? Як два потоки будуть синхронізовані? Все це можливо виконати за допомогою відповідних викликів API. У моїй книзі Visual Basic 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 до такого ступеня, що програмісти могли б інтелектуально правильно застосовувати навіть найбільш просунуті методи. Я застосував це той же самий підхід до моєї книзі Developing ActiveX Components, яка вимагає багато часу для обговорення принципів ActiveX, COM і об'єктно-орієнтованого програмування перед описом подробиць реалізації цієї технології.

Багато що з моєї кар'єри на ниві Visual Basic і багато з діяльності у фірмі Desaware, засноване на навчанні Visual Basic програмістів просунутим методам. Читач, хто надихнув мене на написання цієї статті, критикуючи мене за стримування технології багатопоточності, пропустив точку.

Так, я навчаю і демонструю просунуті методи програмування - але я намагаюся ніколи не пропустити велику картинку. Просунуті методи, яким я навчаю, повинні бути несуперечливі з правилами та специфікаціями Windows. Вони повинні бути такими безпечними, наскільки це можливо. Вони повинні бути стандартними для кінцевому рахунку. Вони не повинні руйнуватися, коли змінюються Windows або Visual Basic.

Я можу вимагати тільки часткового успіху, тому що Microsoft може змінити правила гри кожного разу, коли їм здасться, що це необхідно. Але я завжди пам'ятаю про це і пробую попереджати людей, коли я думаю, що можу проштовхнути обмеження.

Я сподіваюся, що наведене тут обговорення багатопотокового режиму показує небезпеки застосування "простих методів" без гарного розуміння основної технології.

Я не можу обіцяти, що використання apartment model версії CreateThread є абсолютно коректним, але моє розуміння проблеми і досвід показують, що це безпечно.

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

Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Курсова
71.8кб. | скачати


Схожі роботи:
Програмування на Visual Basic
Основи Visual Basic 50
Завдання з програмування на Visual Basic
Створення тесту на Visual Basic
Робота в середовищі Visual Basic
Квитки з інформатики 10 клас Visual Basic
Електронний довідник з вивчення Visual Basic
Мова програмування Visual Basic for Applications
Адресна книга на мові Visual Basic
© Усі права захищені
написати до нас