Иллюстрированный самоучитель по VB.NET

       

Создание потоков


Создание потоков

Начнем с элементарного примера. Допустим, вы хотите запустить в отдельном потоке процедуру, которая в бесконечном цикле уменьшает значение счетчика. Процедура определяется в составе класса:

Public Class WillUseThreads

Public Sub SubtractFromCounter()

Dim count As Integer

Do While True count -= 1

Console.WriteLlne("Am in another thread and counter ="

& count)

Loop

End Sub

End Class



Поскольку условие цикла Do остается истинным всегда, можно подумать, что ничто не помешает выполнению процедуры SubtractFromCounter. Тем не менее в многопоточном приложении это не всегда так.

В следующем фрагменте приведена процедура Sub Main, запускающая поток, и команда Imports:

Option Strict On Imports System.Threading Module Modulel

Sub Main()

1 Dim myTest As New WillUseThreads()

2 Dim bThreadStart As New ThreadStart(AddressOf _

myTest.SubtractFromCounter)

3 Dim bThread As New Thread(bThreadStart)

4 ' bThread.Start()

Dim i As Integer

5 Do While True

Console.WriteLine("In main thread and count is " & i) i += 1

Loop

End Sub

End Module

Давайте последовательно разберем наиболее принципиальные моменты. Прежде всего процедура Sub Man n всегда работает в главном потоке (main thread). В програм-мах .NET всегда работают минимум два потока: главный и поток сборки мусора. В строке 1 создается новый экземпляр тестового класса. В строке 2 мы создаем делегат ThreadStart и передаем адрес процедуры SubtractFromCounter экземпляра тестового класса, созданного в строке 1 (эта процедура вызывается без параметров). Благодаря импортированию пространства имен Threading длинное имя можно не указывать. Объект нового потока создается в строке 3. Обратите внимание на передачу делегата ThreadStart при вызове конструктора класса Thread. Некоторые программисты предпочитают объединять эти две строки в одну логическую строку:

Dim bThread As New Thread(New ThreadStarttAddressOf _

myTest.SubtractFromCounter))

Наконец, строка 4 «запускает» поток, для чего вызывается метод Start экземпляра класса Thread, созданного для делегата ThreadStart.
Вызывая этот метод, мы указываем операционной системе, что процедура Subtract должна работать в отдельном потоке.

Слово «запускает» в предыдущем абзаце заключено в кавычки, поскольку в этом случае наблюдается одна из многих странностей многопоточного программирования: вызов Start не приводит к фактическому запуску потока! Он всего лишь сообщает, что операционная система должна запланировать выполнение указанного потока, но непосредственный запуск находится вне контроля программы. Вам не удастся начать выполнение потоков по своему усмотрению, потому что выполнением потоков всегда распоряжается операционная система. В одном из дальнейших разделов вы узнаете, как при помощи приоритета заставить операционную систему побыстрее запустить ваш поток.

На рис. 10.1 показан пример того, что может произойти после запуска программы и ее последующего прерывания клавишей Ctrl+Break. В нашем случае новый поток запустился лишь после того, как счетчик в главном потоке увеличился до 341!



Рис. 10.1. Простая многопоточная программно время работы

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



Рис. 10.2. Переключение между потоками в простой многопоточной программе

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


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

Поскольку в схемах квантования всех версий Windows, в которых работает .NET, каждо-му потоку выделяется минимальный квант времени, в программировании .NET проблемы с монопольным захватом процессора не столь серьезны. С другой стороны, если среда .NET когда-нибудь будет адаптирована для других систем, ситуация может измениться.

Если включить следующую строку в нашу программу перед вызовом Start, то даже потоки, обладающие минимальным приоритетом, получат некоторую долю процессорного времени:

bThread.Priority = ThreadPriority.Highest



Рис. 10.3. Поток с максимальным приоритетом обычно начинает работать быстрее



Рис. 10.4. Процессор предоставляется и потокам с более низким приоритетом

Команда назначает новому потоку максимальный приоритет и уменьшает приоритет главного потока. Из рис. 10.3 видно, что новый поток начинает работать быстрее, чем прежде, но, как показывает рис. 10.4, главный поток тоже получает управление (правда, очень ненадолго и лишь после продолжительной работы потока с вычитанием). При запуске программы на ваших компьютерах будут получены результаты, похожие на показанные на рис. 10.3 и 10.4, но из-за различий между нашими системами точного совпадения не будет.

В перечисляемый тип ThreadPrlority входят значения для пяти уровней приоритета:

ThreadPriority.Highest

ThreadPriority.AboveNormal

ThreadPrlority.Normal

ThreadPriority.BelowNormal

ThreadPriority.Lowest


Содержание раздела