Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all articles
Browse latest Browse all 1529

Pure VB6 Multithreading example with a standalone ActiveX EXE application

$
0
0
This project is a standalone ActiveX EXE application that shows how to create multiple threads to perform lengthy tasks and then communicate the results via "event-like" callbacks. In this example, we are using 5 threads and each thread is checking whether a rather large number is prime or not. The project is set up so you can easily add more tasks for each thread to perform. All threads are placed in an array so we can easily communicate with each of them via their index in the array:

Name:  VB_MultiThreading.png
Views: 40
Size:  12.7 KB

The project is IDE-safe, you can break or stop at any time without crashes, however the IDE runs single-threaded so you will see the same "Thread ID" across the board and you cannot check more than one number at a time. When compiled into an executable, each thread will be different as seen in the above screenshot and you can check more numbers at the same time without any problems.

Be careful when checking very large numbers. If they are composite then the result will be shown very fast but if you hit a large prime (larger than what you see in the screenshot) then the algorithm will take a VERY long time to finish. Suggestions for improvement are welcome in this area! :D

frmVBMultiThreading - The main form containing the user interface.
Code:

Option Explicit

Implements IVBThread

Private Const THREADS_COUNT As Long = 5

Private Const SND_ASYNC As Long = &H1, SND_MEMORY As Long = &H4

Private Declare Function sndPlaySound Lib "winmm" Alias "sndPlaySoundA" (lpszSoundName As Any, ByVal uFlags As Long) As Long

Private Threads() As clsVBMultiThreading, byteJobsDone() As Byte, sProjectName As String

Private Sub cmdCheckPrime_Click(Index As Integer)
    lblFactors(Index) = vbNullString: lblFactors(Index).Refresh
    Threads(Index).StartAsyncTask eTaskCheckPrime, CDec(txtPrime(Index))
End Sub

Private Sub txtPrime_KeyPress(Index As Integer, KeyAscii As Integer)
    If (KeyAscii < vbKey0 Or KeyAscii > vbKey9) And (KeyAscii <> vbKeyReturn) And (KeyAscii <> vbKeyBack) Then KeyAscii = 0
    If KeyAscii = vbKeyReturn Then KeyAscii = 0: Call cmdCheckPrime_Click(Index)
End Sub

Private Sub Form_Activate()
Dim i As Long
Static bActivate As Boolean
    If bActivate Then Exit Sub Else bActivate = True
    For i = LBound(Threads) To UBound(Threads)
        Set Threads(i) = CreateObject(sProjectName & ".clsVBMultiThreading")
        Set Threads(i).Callback(i) = Me
        lblThreadID(i) = Threads(i).ThreadID
    Next i
End Sub

Private Sub Form_Load()
    sProjectName = GetProjectName: ReadFile "JobsDone.wav", byteJobsDone
    ReDim Threads(0 To THREADS_COUNT - 1)
End Sub

Private Sub Form_Unload(Cancel As Integer)
Dim i As Long
    For i = LBound(Threads) To UBound(Threads)
        Set Threads(i) = Nothing
    Next i
End Sub

Private Sub IVBThread_JobDone(ByVal Index As Long, Optional ByVal vParam As Variant, Optional vReturnValue As Variant)
    If Not IsEmpty(vParam) Then
        lblFactors(Index) = ArrayToString(vParam)
        If vReturnValue Then lblFactors(Index) = lblFactors(Index) & " is Prime!"
    End If
    sndPlaySound byteJobsDone(0), SND_MEMORY Or SND_ASYNC
End Sub

clsVBMultiThreading - Set to "MultiUse", each instance of this class will be created in a separate Thread!
Code:

Option Explicit

Public Enum eTasks
    eTaskUndefined = 0
    eTaskCheckPrime = 1
    'Other tasks to be defined here
End Enum

Private Const WM_MOUSEMOVE As Long = &H200

Private Declare Function PostMessageVal Lib "user32" Alias "PostMessageW" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Private WithEvents objThread As Form, m_Callback As IVBThread, m_Index As Long, m_CurrentTask As eTasks, m_ThreadID As Long, m_Params As Variant

Public Property Get Callback(Optional Index As Long) As IVBThread
    Index = m_Index: Set Callback = m_Callback
End Property

Public Property Set Callback(Index As Long, objCallback As IVBThread)
    m_Index = Index: Set m_Callback = objCallback
End Property

Public Sub StartAsyncTask(eTask As eTasks, ParamArray ParamsArray() As Variant)
Dim i As Long
    m_Params = Empty
    For i = LBound(ParamsArray) To UBound(ParamsArray)
        If i = LBound(ParamsArray) Then ReDim m_Params(LBound(ParamsArray) To UBound(ParamsArray))
        m_Params(i) = ParamsArray(i)
    Next i
    m_CurrentTask = eTask
    PostMessageVal objThread.hWnd, WM_MOUSEMOVE, 0&, 0& ' Start the task asynchronously
End Sub

Public Property Get ThreadID() As Long
    ThreadID = m_ThreadID
End Property

Private Sub JobDone(Optional ByVal vParam As Variant, Optional vReturnValue As Variant)
    If Not (m_Callback Is Nothing) Then m_Callback.JobDone m_Index, vParam, vReturnValue
End Sub

Private Sub Class_Initialize()
    Set objThread = New frmThread
    Load objThread: m_ThreadID = App.ThreadID
End Sub

Private Sub Class_Terminate()
    If objThread.IsLoaded Then Unload objThread ' Check if a form is already loaded without accidentally loading it
    Set objThread = Nothing
End Sub

Private Sub objThread_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Select Case m_CurrentTask
        Case eTaskCheckPrime
            If Not IsEmpty(m_Params) Then
                Dim vFactors As Variant, bIsPrime As Boolean
                bIsPrime = IsPrime(m_Params(0), vFactors)
                JobDone vFactors, bIsPrime
            End If
    End Select
End Sub

IVBThread - simple interface class to declare callback "events" raised by each thread in the array of threads:
Code:

Option Explicit

Public Sub JobDone(ByVal Index As Long, Optional ByVal vParam As Variant, Optional vReturnValue As Variant)

End Sub

mdlVBMultiThreading - module containing our "IsPrime" function that does all the work in each thread
Code:

Option Explicit

Public Sub Main()
    If Not App.TaskVisible Then Exit Sub ' Additional threads are re-entrant, meaning they will always execute the "Sub Main" so just exit the sub
    frmVBMultiThreading.Show
End Sub

Public Function ArraySort(vArray As Variant) As Variant
Dim i As Long, bSwitch As Boolean, vTemp As Variant
    Do
        bSwitch = False
        For i = LBound(vArray) To UBound(vArray) - 1
            If vArray(i) > vArray(i + 1) Then
                vTemp = vArray(i): vArray(i) = vArray(i + 1): vArray(i + 1) = vTemp: bSwitch = True
            End If
        Next i
    Loop While bSwitch
    ArraySort = vArray
End Function

Public Function ArrayToString(vArray As Variant, Optional sDelimiter As String = " ") As String
Dim i As Long
    For i = LBound(vArray) To UBound(vArray)
        ArrayToString = ArrayToString & vArray(i)
        If i < UBound(vArray) Then ArrayToString = ArrayToString & sDelimiter
    Next i
End Function

Public Function BigMod(vDividend As Variant, vDivisor As Variant) As Variant
    BigMod = vDividend - Int(vDividend / vDivisor) * vDivisor
End Function

Public Function GetProjectName() As String
On Error Resume Next
    Err.Raise 5: GetProjectName = Err.Source: Err.Clear
End Function

Public Function IsPrime(ByVal n As Variant, Optional arrFactors As Variant, Optional bNoRepeats As Boolean = True) As Boolean
Dim vFactors As Variant, i As Variant, j As Long, lCount As Long, factorCount As Long
    ReDim vFactors(0 To 31)
    If n <= 1 Then Exit Function
    While BigMod(n, 2) = 0
        n = n / 2: vFactors(factorCount) = 2: factorCount = factorCount + 1
        If factorCount > UBound(vFactors) Then ReDim Preserve vFactors(0 To factorCount)
    Wend
    While BigMod(n, 3) = 0
        n = n / 3: vFactors(factorCount) = 3: factorCount = factorCount + 1
        If factorCount > UBound(vFactors) Then ReDim Preserve vFactors(0 To factorCount)
    Wend
    While BigMod(n, 5) = 0
        n = n / 5: vFactors(factorCount) = 5: factorCount = factorCount + 1
        If factorCount > UBound(vFactors) Then ReDim Preserve vFactors(0 To factorCount)
    Wend
    i = CDec(7)
    While i <= n / i
        If BigMod(n, i) = 0 Then
            n = n / i: vFactors(factorCount) = i: factorCount = factorCount + 1
        ElseIf BigMod(n, (i + 4)) = 0 Then
            n = n / (i + 4): vFactors(factorCount) = i + 4: factorCount = factorCount + 1
        ElseIf BigMod(n, (i + 6)) = 0 Then
            n = n / (i + 6): vFactors(factorCount) = i + 6: factorCount = factorCount + 1
        ElseIf BigMod(n, (i + 10)) = 0 Then
            n = n / (i + 10): vFactors(factorCount) = i + 10: factorCount = factorCount + 1
        ElseIf BigMod(n, (i + 12)) = 0 Then
            n = n / (i + 12): vFactors(factorCount) = i + 12: factorCount = factorCount + 1
        ElseIf BigMod(n, (i + 16)) = 0 Then
            n = n / (i + 16): vFactors(factorCount) = i + 16: factorCount = factorCount + 1
        ElseIf BigMod(n, (i + 22)) = 0 Then
            n = n / (i + 22): vFactors(factorCount) = i + 22: factorCount = factorCount + 1
        ElseIf BigMod(n, (i + 24)) = 0 Then
            n = n / (i + 24): vFactors(factorCount) = i + 24: factorCount = factorCount + 1
        Else
            i = i + 30
        End If
        If factorCount > UBound(vFactors) Then ReDim Preserve vFactors(0 To factorCount)
    Wend
    If n > 1 Then vFactors(factorCount) = n: factorCount = factorCount + 1
    If Not IsMissing(arrFactors) Then
        n = 0
        If bNoRepeats Then
            For i = 0 To factorCount - 1
                If vFactors(i) > 0 Then
                    lCount = 1
                    For j = i + 1 To factorCount - 1
                        If vFactors(j) = vFactors(i) Then
                            lCount = lCount + 1: vFactors(j) = 0
                        End If
                    Next j
                    vFactors(n) = vFactors(i) ^ lCount: n = n + 1
                End If
            Next i
            ReDim Preserve vFactors(0 To n - 1): arrFactors = ArraySort(vFactors)
        Else
            ReDim Preserve vFactors(0 To factorCount - 1): arrFactors = vFactors
        End If
    End If
    IsPrime = factorCount = 1
End Function

Public Sub ReadFile(sFileName As String, vData As Variant, Optional sCodePage As String = "_autodetect_all")
Dim oStream As Object
    Set oStream = CreateObject("ADODB.Stream")
    With oStream
        Select Case VarType(vData)
            Case vbArray Or vbByte
                .Type = 1: .Open: .LoadFromFile sFileName: vData = .Read
            Case vbString
                .Charset = sCodePage: .Type = 2: .Open: .LoadFromFile sFileName: vData = .ReadText
        End Select
        If .state <> 0 Then .Close
    End With
End Sub

frmThread - Hidden form loaded in each thread only for the purpose of starting thread jobs "asynchronously".
Code:

Option Explicit

Private m_bIsLoaded As Boolean

Public Property Get IsLoaded() As Boolean
    IsLoaded = m_bIsLoaded
End Property

Private Sub Form_Load()
    m_bIsLoaded = True
    Caption = Caption & App.ThreadID
End Sub

Here is the Demo Project: VB_MultiThreading.zip. That's all there is to Multithreading in VB6! :)
Attached Images
 
Attached Files

Viewing all articles
Browse latest Browse all 1529

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>