Content Supported by Sourcelens Consulting

VERSION 5.00
Begin VB.Form frmThread 
   BorderStyle     =   1  'Fixed Single
   Caption         =   "Multithreading Demo"
   ClientHeight    =   4950
   ClientLeft      =   4140
   ClientTop       =   1470
   ClientWidth     =   7095
   LinkTopic       =   "Form2"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   4950
   ScaleWidth      =   7095
   Begin VB.Timer tmrShort 
      Enabled         =   0   'False
      Interval        =   1000
      Left            =   2280
      Top             =   3840
   End
   Begin VB.CommandButton cmdCancel 
      Caption         =   "&Cancel Long Tasks"
      Height          =   375
      Left            =   240
      TabIndex        =   8
      Top             =   4440
      Width           =   2895
   End
   Begin VB.TextBox txtN 
      Height          =   375
      Left            =   2760
      MaxLength       =   1
      TabIndex        =   6
      Text            =   "4"
      Top             =   2160
      Width           =   375
   End
   Begin VB.CommandButton cmdNLong 
      Caption         =   "...with &N LongTasks"
      Height          =   375
      Left            =   240
      TabIndex        =   5
      Top             =   2160
      Width           =   2415
   End
   Begin VB.CommandButton cmdShortLong 
      Caption         =   "...with &Long Task"
      Height          =   375
      Left            =   240
      TabIndex        =   4
      Top             =   1680
      Width           =   2895
   End
   Begin VB.CommandButton cmdShortOnly 
      Caption         =   "&Short Tasks, Serialized..."
      Height          =   375
      Left            =   240
      TabIndex        =   3
      Top             =   1200
      Width           =   2895
   End
   Begin VB.CommandButton cmdIDs 
      Caption         =   "List Thread &IDs"
      Height          =   375
      Left            =   240
      TabIndex        =   2
      Top             =   720
      Width           =   2895
   End
   Begin VB.ListBox lstResults 
      Height          =   4575
      Left            =   3360
      TabIndex        =   1
      Top             =   120
      Width           =   3615
   End
   Begin VB.CommandButton cmdXThread 
      Caption         =   "Cross-Thread &Overhead"
      Height          =   375
      Left            =   240
      TabIndex        =   0
      Top             =   240
      Width           =   2895
   End
   Begin VB.Label Label1 
      Caption         =   "Your experiments here..."
      Height          =   255
      Left            =   240
      TabIndex        =   7
      Top             =   2760
      Width           =   2895
   End
End
Attribute VB_Name = "frmThread"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
' The multithreading demo:
'   - Shows thread ids and number of objects
'       on a thread (this will be more
'       interesting if you compile MTCoffee.exe
'       with a thread pool size of 3 or 4).
'   - Compares call overhead for same-thread
'       vs. cross-thread calls.
'   - Time per iteration for serial short
'       tasks (see Coffee object defined in
'       MTCoffee.cls).
'   - Times per iteration for a long task, and
'       for serial short tasks run at the same
'       time.
'   - Times per iteration for a number of long
'       tasks, with serial short tasks running
'       at the same time.
'
' You can add your own tests to this framework.
'   You may find it interesting to compare the
'   behavior of tasks that block (such as
'   database queries on remote computers, or
'   large file transfers).  On a computer with
'   a single processor, such tasks will behave
'   much better than the computation-intensive
'   tasks used in the examples above.  Threads
'   that perform computation-intensive tasks
'   compete with each other for the machine's
'   single processor, and so their performance
'   suffers as the number of active threads
'   increases.
'
' For more information, see "Scalability and
'   Multithreading," in "Building Code
'   Components" in Books Online.
 
' These constants control the relative size of
'   a long task and a short task.  You may need
'   to adjust these for the speed of your
'   processor.
Const SHORTTASKSIZE = 50000
Const LONGTASKSIZE = 2000000

' Array of Coffee objects.
Private macfe(1 To 20) As Coffee

' Collection of CoffeeTracker objects.
Public CoffeeTrackers As New Collection

' Cancel flag.
Public CancelAll As Boolean

' How many short tasks to run.
Private mintHowManyShort As Integer

Private Sub cmdCancel_Click()
    ' When the user clicks Cancel, set a
    '   flag that CoffeeTracker can use
    '   to cancel all long tasks when they
    '   raise their next Progress event.
    CancelAll = True
End Sub

' Run a few short tasks serially, to get a
'   feel for their speed when not competing
'   for the processor.
Private Sub cmdShortOnly_Click()
    lstResults.Clear
    CancelAll = False
    mintHowManyShort = 10
    tmrShort.Interval = 250
    tmrShort.Enabled = True
End Sub

' Run a long task, then run short tasks
'   serially while it's running.
Private Sub cmdShortLong_Click()
    Dim cft As CoffeeTracker
    
    lstResults.Clear
    CancelAll = False
    Set cft = NewTracker(macfe(1).ThreadID, LONGTASKSIZE)
    Set cft.Coffee = macfe(1)
    Call macfe(1).StartLongTask(LONGTASKSIZE)
    '
    ' Line up some short tasks to run (one
    '   every quarter second) while task
    '   runs.
    mintHowManyShort = 10
    tmrShort.Enabled = True
    tmrShort.Interval = 250
End Sub

' Start N long tasks (1 - 9), then run a series
'   of short tasks.
Private Sub cmdNLong_Click()
    Dim intCt As Integer
    Dim cft As CoffeeTracker
    
    lstResults.Clear
    CancelAll = False
    For intCt = 1 To CLng("0" & txtN)
        Set cft = NewTracker(macfe(intCt).ThreadID, LONGTASKSIZE)
        Set cft.Coffee = macfe(intCt)
        Call macfe(intCt).StartLongTask(LONGTASKSIZE)
    Next
    '
    ' Line up some short tasks to run while
    '   the long ones run.
    mintHowManyShort = 10 + CLng("0" & txtN)
    tmrShort.Enabled = True
    tmrShort.Interval = 250
End Sub

' Compare the call overhead for calls to an
'   object on the same thread, vs. calls to
'   an object on another thread.
'
Private Sub cmdXThread_Click()
    Dim cfeSame As Coffee
    Dim cfeNew As Coffee
    
    cmdXThread.Caption = "Working..."
    cmdXThread.Enabled = False
    
    ' Create a Coffee object on another thread.
    Set cfeNew = macfe(10).GetCoffeeOnNewThread
    ' In case of thread pooling; except in
    '   the degenerate case of one thread in
    '   the pool, this should get a different
    '   thread the second time.
    If cfeNew.ThreadID = macfe(10).ThreadID Then
        Set cfeNew = macfe(10).GetCoffeeOnNewThread
        If cfeNew.ThreadID = macfe(10).ThreadID Then
            MsgBox "Unable to run comparison between same-thread and cross-thread calls; can't get an object on another thread."
            Exit Sub
        End If
    End If
    '
    ' Create a Coffee object on the same thread.
    Set cfeSame = macfe(10).GetCoffeeOnSameThread
    
    ' Use the newly created coffee objects to
    '   perform the test.
    MsgBox "Same thread:  " & macfe(10).CallAnotherCoffee(cfeSame) & " sec/call" & vbCrLf _
        & "Cross-thread:  " & macfe(10).CallAnotherCoffee(cfeNew) & " sec/call"
    
    cmdXThread.Caption = "Cross-Thread Overhead"
    cmdXThread.Enabled = True
    '
    ' The Coffee objects created for this test
    '   are terminated when cfeNew and cfeSame
    '   go out of scope at the end of this
    '   procedure.
End Sub

' List the thread IDs of the Coffee objects
'   created when this form loaded.
'
Private Sub cmdIDs_Click()
    Dim intCt As Integer
    lstResults.Clear
    For intCt = 1 To 10
        lstResults.AddItem macfe(intCt).ThreadID _
            & "  (" & macfe(intCt).NumberOnThread & " on thread)"
    Next
End Sub

' Create a series of Coffee objects, each on
'   its own thread.  (If you recompile MTCoffee
'   with a thread pool less than 10, some of
'   these will share thread and global state.)
'
Private Sub Form_Load()
    Dim intCt As Integer
    For intCt = 1 To 10
        Set macfe(intCt) = New Coffee
    Next
    Form1.cmdMT.Enabled = True
    Form1.cmdMT.MousePointer = vbDefault
End Sub

Private Sub Text1_KeyPress(KeyAscii As Integer)
    Select Case KeyAscii
        Case 48 To 57, 8
        Case Else
            Beep
            KeyAscii = 0
    End Select
End Sub

' Provide unique keys for CoffeeTrackers.
'
Private Function NewKey() As String
    Static lngLastKey As Long
    lngLastKey = lngLastKey + 1
    NewKey = "K" & lngLastKey
End Function

' Add a new CoffeeTracker.  Properly speaking,
'   this should be a method of a CoffeeTrackers
'   collection class.
'
Private Function NewTracker(ByVal ThreadID As Long, _
        ByVal Size As Long) As CoffeeTracker
        
    Dim cft As New CoffeeTracker
    '
    ' Cache the thread ID of the Coffee object
    '   the tracker will be keeping track of.
    cft.ThreadID = ThreadID
    '
    ' Set the size of the task assigned to the
    '   Coffee object the tracker will track.
    cft.Size = Size
    '
    ' Give the tracker a unique key for the
    '   collection.
    cft.ID = NewKey
    '
    ' Put the new tracker into a collection.
    CoffeeTrackers.Add cft, cft.ID
    '
    ' Return a reference to the new tracker.
    Set NewTracker = cft
End Function

' Timer is used to start a series of short
'   tasks, at regular intervals, using one
'   Coffee object (that is, a single thread).
'   Before starting a new task, it checks to
'   see whether the preceding task is done.
'
' If you run MTCoffee in the development
'   environment, comment out the code that
'   checks for preceding task completion.
'
Private Sub tmrShort_Timer()
    Static intCt As Integer
    Static strWaitingFor As String
    Dim cft As CoffeeTracker
    
    ' If strWaitingFor contains a key, then
    '   the Coffee object is (or was) performing
    '   a task, and the static string variable
    '   contains the key of the CoffeeTracker
    '   that's watching it.
    If strWaitingFor <> "" Then
        On Error Resume Next
        '
        ' If the CoffeeTracker we're waiting
        '   for has dropped out of the
        '   collection, then an error will
        '   occur -- meaning it's time to
        '   start another one.
        Set cft = CoffeeTrackers(strWaitingFor)
        If Err.Number = 0 Then Exit Sub
    Else
        ' If the static string variable is
        '   empty, then the previous series
        '   of short tasks is complete.  A
        '   new series is beginning, so reset
        '   the static counter (intCt).
        intCt = 0
    End If
    
    If Not CancelAll Then
        intCt = intCt + 1
        '
        ' Create a CoffeeTracker to wait
        '   for the request to finish.
        Set cft = NewTracker(macfe(10).ThreadID, SHORTTASKSIZE)
        '
        ' Give the CoffeeTracker its Coffee
        '   object to watch.
        Set cft.Coffee = macfe(10)
        '
        ' Begin the task.
        Call macfe(10).StartLongTask(SHORTTASKSIZE)
        '
        ' Prepare to wait for the
        '   CoffeeTracker.
        strWaitingFor = cft.ID
    End If
    '
    ' Check to see if we've completed the
    '   series of short tasks, or if we've
    '   been stopped by the Cancel button:
    If (intCt >= mintHowManyShort) Or CancelAll Then
        intCt = 0
        tmrShort.Enabled = False
        strWaitingFor = ""
    End If
End Sub