티스토리 뷰

프로그래밍

VB 쓰레드 사용하기

야라바 2016. 3. 14. 14:16
728x90

C/C++을 사용하던, C#을 사용하던, 비주얼베이직을 사용하던 일반적인 프로그램을 제작하는 경우라면 쓰레드는 개발자에게 그렇게 친숙한 기술은 아닙니다. 그렇지만 복잡한 업무를 수행하는 대규모 프로그램이나 속도가 생명인 프로세스에서는 쓰레드 기술은 필수적인 기술입니다. 대규모의 트래픽을 처리해야 하는 DBMS, 고속으로 다양한 업무를 처리하는 미들웨어와 같은 프로그램이 대표적인 예라 하겠습니다.

    • 여러 서버의 파일을 동시에 다운로드하기

    • 여러 홈페이지의 내용을 한꺼번에 분석하기

    • 화면 접수와 처리를 분리하면서 동시에 수행하기

위의 예제들은 일반 프로그램에서도 충분히 적용 가능성이 있는 사례로 이런 처리 흐름에서는 쓰레드 기술은 필수 요소라 할 수 있습니다. 물론 쓰레드를 사용하지 않고 프로그램을 분리하는 방법도 가능합니다. 그렇지만 프로그램간에 소통이 쉽지 않고 그 비용 조차 작지 않습니다. 그렇지만 쓰레드를 사용하면 메모리를 쉽게 공유할 수 있고 각 쓰레드를 적절하게 통제할 수 있다는 장점이 있습니다. 이해를 위해서 동시에 여러 개의 언론사 사이트를 접속해서 현 시점의 주요 뉴스를 동시에 가져오는 예제 프로그램을 작성해 보겠습니다.

우선은 위의 그림과 같이 읽어 올 언론사 페이지의 주소를 담은 리스트 박스와 각 페이지에 대한 작업 상황을 표시할 리스트 박스, 그리고 작업 상황 리스트 박스에서 읽어오기가 끝난 것을 더블클릭하면 해당 언론사 페이지의 내용을 출력할 텍스트 박스와 작업 시작을 지시할 버튼으로 화면을 구성합니다. 페이지 주소는 각 언론사의 RSS 서비스 주소를 참조했습니다. 비베 닷넷으로 웹 페이지 읽기는 "VB로 웹페이지 읽어오기 - 웹 스크랩"를 참조하시면 됩니다.

Imports System.Net
Imports System.IO
Imports System.Text.RegularExpressions

Public Class WebScrap
    Public Thidx As Integer = -1
    Public URL As String = ""
    Public str As String = ""
    Public errmsg As String = ""
    Public Event EndScrap(ByVal Thnum As Integer, ByVal Result As Boolean)

    Sub GoScrap()
        If URL = "" Then
            errmsg = "URL을 입력하세요"
            RaiseEvent EndScrap(Thidx, False)
            Return
        End If
        Try
            Dim wresp As WebResponse
            Dim wreq As WebRequest = HttpWebRequest.Create(URL)

            wresp = wreq.GetResponse()

            Using sr As New StreamReader(wresp.GetResponseStream())
                str = sr.ReadToEnd()
                sr.Close()
            End Using

            str = Regex.Replace(str, "<.*?>", "")
            RaiseEvent EndScrap(Thidx, True)
        Catch ex As Exception
            errmsg = ex.Message
            RaiseEvent EndScrap(Thidx, False)
        End Try
    End Sub
End Class

"VB로 웹페이지 읽어오기 - 웹 스크랩"를 참조하여 각 쓰레드에서 웹페이지 읽어오기를 수행하는 클래스를 위와 같이 작성합니다. 여러 개의 독립된 쓰레드로 수행할 부분은 GoScrap() 서브루틴으로 일반적인 함수라면 함수를 호출한 곳으로 값을 리턴하는 방식으로 함수의 작업을 끝내겠지만 별도의 쓰레드로 동작하는 부분이므로 이벤트를 발생시키는 방식을 적용하여 작업 종료 즉시 결과를 통보하도록 했습니다. 위의 코드에서는 EndScrap이라는 사용자 이벤트를 정의했는데 이 이벤트를 처리할 핸들러를 버튼 클릭 핸들러를 작성하듯이 작성하면 됩니다. 쓰레드 수행에 필요한 웹페이지 주소, 작업 결과 등은 클래스 속성을 활용하도록 했습니다.

Imports System.Threading

Public Class Form1
    Const PRC_COUNT As Integer = 10
    Dim webprc(PRC_COUNT) As WebScrap
    Dim webthread(PRC_COUNT) As Thread

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        For i As Integer = 0 To PRC_COUNT - 1
            webprc(i) = New WebScrap
            webthread(i) = New System.Threading.Thread(AddressOf webprc(i).GoScrap)
            webprc(i).Thidx = i
            webprc(i).URL = ListBox1.Items(i)
            ListBox2.Items(i) = "작업중"
            AddHandler webprc(i).EndScrap, AddressOf ProcessEndScrap
            webthread(i).Start()
        Next
    End Sub

    Sub ProcessEndScrap(ByVal Idx As Integer, ByVal Result As Boolean)
        If Idx >= 0 Then
            If Result Then
                SetListbox(Idx, "작업 완료")
            Else
                SetListbox(Idx, webprc(Idx).errmsg)
            End If
        End If
    End Sub

    Delegate Sub SetListboxCallback([Idx] As Integer, [msg] As String)

    Sub SetListbox(ByVal [Idx] As Integer, ByVal [msg] As String)
        If ListBox2.InvokeRequired Then
            Dim d As New SetListboxCallback(AddressOf SetListbox)
            Invoke(d, New Object() {[Idx], [msg]})
        Else
            ListBox2.Items(Idx) = msg
        End If
    End Sub

    Private Sub ListBox2_DoubleClick(sender As System.Object, e As System.EventArgs) Handles ListBox2.DoubleClick
        If ListBox2.SelectedIndex < 0 Then Return
        If ListBox2.Items(ListBox2.SelectedIndex) <> "작업 완료" Then Return
        TextBox2.Text = webprc(ListBox2.SelectedIndex).str
    End Sub
End Class

위의 코드는 Form1이 코드로 [가져오기] 버튼을 클릭하면 쓰레드 가동을 위한 클래스 인스턴스 생성과 쓰레드를 생성하는 것으로 작업을 시작합니다. 리스트 박스에 있는 언론사 페이지의 주소와 인덱스를 쓰레드 클래스의 속성에 설정하고 .Start()를 호출하여 독립적인 쓰레드가 동작하도록 지시합니다. 쓰레드 클래스에서 Start() 메소드 외에도 다음과 같은 쓰레드 컨트롤 메소드를 사용할 수 있습니다.

  • Start() : 쓰레드의 실행을 기동시킴

  • Abort() : 쓰레드를 중단시킴

  • Suspend() : 쓰레드 수행을 잠시 유보 시킴

  • Resume() : 유보한 쓰레드 수행을 재개시킴

  • Join() : 해당 쓰레드의 종료 또는 중단을 기다림

  • Sleep(밀리초) : 지정한 밀리초 만큼 해당 쓰레드를 멈춤

쓰레드를 가동시키면서 "작업중"으로 표시한 리스트 박스의 내용은 개별 쓰레드가 종료되면서 "작업 완료" 또는 오류메시지로 바뀌는데 그 신호는 AddHandler로 설정한 쓰레드의 이벤트를 처리 로직인 ProcessEndScrap 입니다. 그런데 만약에 ProcessEndScrap에서 직접 작업 상태를 나타내는 ListBox2에 접근하면 아래와 같은 예외가 발생합니다.

"InvalidOperationException"의 메시지 "크로스 스레드 작업이 잘못되었습니다. 'ListBox2' 컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 액세스되었습니다"의 내용 처럼 페이지를 읽어오는 쓰레드와 사용자 인터페이스를 처리하는 쓰레드가 다름으로 인하여 발생하는 예외는 SetListbox 함수처럼 컨트롤의 InvokeRequired 속성 값에 따라 Invoke 처리하는 방식을 적용해야 정상적으로 프로그램을 동작시킬 수 있습니다.

프로그램을 실행시키면 위의 그림처럼 사이트의 속도와 환경에 따라 응답 시간의 차이가 발생하고 그 결과로 각 쓰레드의 "작업 완료" 표시도 차이가 있게 됩니다.

"작업 완료"상태의 쓰레드에 대해서 더블클릭하면 위의 그림과 같이 해당 언론사에서 가져온 데이터를 하단 텍스트 박스에 출력합니다.

728x90
댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함