Многопоточность в графических программах
Многопоточность в графических программах
Наше обсуждение многопоточности в приложениях с графическим интерфейсом начнется с примера, поясняющего, для чего нужна многопоточность в графических приложениях. Создайте форму с двумя кнопками 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 работает, при ее нажатии мы просто завершаем программу.