Иллюстрированный самоучитель по VB .NET
Многопоточность
в графических программах
Наше обсуждение
многопоточности в приложениях с
графическим интерфейсом начнется с
примера, поясняющего, для чего
нужна многопоточность в
графических приложениях. Создайте
форму с двумя кнопками Start (btnStart) и
Cancel (btnCancel), как показано на рис. 10.9.
При нажатии кнопки Start создается
класс, который содержит случайную
строку из 10 миллионов символов и
метод для подсчета вхождений буквы
«Е» в этой длинной строке. Обратите
внимание на применение класса
StringBuilder, повышающего эффективность
создания длинных строк.
Шаг 1
Поток 1 замечает,
что данных для него нет. Он вызывает
Wait, снимает блокировку и переходит
в очередь ожидания
Шаг 2
При снятии
блокировки поток 2 или поток 3
выходит из очереди блокировки и
входит в синхронизированный блок,
устанавливая блокировку
ШагЗ
Допустим, поток
3 входит в синхронизированный блок,
создает данные и вызывает Pulse-Pulse All.
Сразу же после
его выхода из блока и снятия
блокировки поток 1 перемещается в
очередь выполнения. Если поток 3
вызывает Pluse, в очередь выполнения
переходит только один поток, при
вызове Pluse All в очередь выполнения
переходят все потоки.
Рис. 10.8. Проблема
«поставщик/потребитель»
Рис. 10.9. Многопоточность
в простом приложении с графическим
интерфейсом
Imports System.Text
Public Class
RandomCharacters
Private m_Data As
StringBuilder
Private m_CountDone
As Boolean
Private mjength,
m_count As Integer
Public Sub New(ByVal
n As Integer)
m_Length = n -1
m_Data = New
StringBuilder(m_length) MakeString()
End Sub
Private Sub
MakeString()
Dim i As Integer
Dim myRnd As New
Random()
For i = 0 To
m_length
'
Сгенерировать случайное число от 65
до 90,
'
преобразовать его в прописную
букву
' и
присоединить к объекту StringBuilder
m_Data.Append(Chr(myRnd.Next(65.90)))
Next
End Sub
Public Sub
StartCount()
GetEes()
End Sub
Private Sub GetEes()
Dim i As Integer
For i = 0 To
m_length
If m_Data.Chars(i) =
CChar("E") Then
m_count += 1
End If Next
m_CountDone = True
End Sub
Public Readonly
Property GetCount()
As Integer Get
If Not (m_CountDone)
Then
Throw New Exception("Count
not yet done") Else
Return m_count
End If
End Get End Property
Public Readonly
Property IsDone()As
Boolean Get
Return
m_CountDone
End Get
End Property
End Class
С двумя
кнопками на форме связывается
весьма простой код. В процедуре btn-Start_Click
создается экземпляр приведенного
выше класса RandomCharacters,
инкапсулирующего строку с 10
миллионами символов:
Private Sub
btnStart_Click(ByVal sender As System.Object.
ByVal e As System.EventArgs)
Handles btnSTart.Click
Dim RC As New
RandomCharacters(10000000)
RC.StartCount()
MsgBox("The
number of es is " & RC.GetCount)
End Sub
Кнопка Cancel
выводит окно сообщения:
Private Sub
btnCancel_Click(ByVal sender As System.Object._
ByVal e As System.EventArgs)Handles
btnCancel.Click
MsgBox("Count
Interrupted!")
End Sub
При запуске
программы и нажатии кнопки Start
выясняется, что кнопка Cancel не
реагирует на действия пользователя,
поскольку непрерывный цикл не
позволяет кнопке обработать
полученное событие. В современных
программах подобное недопустимо!
Возможны два
решения. Первый вариант, хорошо
знакомый по предыдущим версиям VB,
обходится без многопоточности: в
цикл включается вызов DoEvents. В .NET эта
команда выглядит так:
Application.DoEvents()
В нашем примере
это определенно нежелательно —
кому захочется замедлять программу
десятью миллионами вызовов DoEvents!
Если вместо этого выделить цикл в
отдельный поток, операционная
система будет переключаться между
потоками и кнопка Cancel сохранит
работоспособность. Реализация с
отдельным потоком приведена ниже.
Чтобы наглядно показать, что кнопка
Cancel работает, при ее нажатии мы
просто завершаем программу.