Yet again I am approaching the subject of multithreading in VBA. This time I am coming with an interesting alternative to multithreading in VBA compared to the ones I presented previously. So again let me start by repeating that multithreading is possible in VBA but not in “native” VBA. What do I mean by “native”? I mean that VBA as a programming language does not natively support creating new threads, joining threads etc. However, you can achieve multithreading by using external libraries, modules or scripts.
This post is the result of my previous post EXCEL: Multithreading in VBA using VBscript. In that article I focus on an idea of using the Windows Shell to call multiple VBscript worker threads. Although this allowed me to quite easily achieve multithreading VBA, on the the other hand VBscript turned out to be much slower than VBA therefore even multithreaded algorithms would work slower than a single threaded VBA algorithm. As a result of these findings I decided to leverage VBA worker threads (instead of VBscript threads) via Windows Shell (and VBscript). I know this is fairly complicated so let me explain and continue with a simple example.
How multithreading VBA with VBA worker threads works
As shown above the concept is to replicate as many Excel workbooks as we need threads and then run the macros concurrently via VBscript.
Mulithreading VBA example
This is the code. 60-something lines of code – so not too bad :):
Sub VBAMultithread(maxThreads As Long) Dim thread As Long, threads As Long For threads = 1 To maxThreads startTime = Timer For thread = 1 To threads CreateVBAThread thread, threads, divTabSize Next thread CheckIfFinishedVBA threads Next threads End Sub Sub CreateVBAThread(threadNr As Long, maxThreads As Long, divTabSize As Long) Dim s As String, sFileName As String, wsh As Object, threadFileName As String '---Save a copy of the Excel workbook--- threadFileName = ActiveWorkbook.Path & "Thread_" & maxThreads & "_" & threadNr & ".xls" Call ActiveWorkbook.SaveCopyAs(threadFileName) '---Save the VBscript--- s = "Set objExcel = CreateObject(""Excel.Application"")" & vbCrLf s = s & "Set objWorkbook = objExcel.Workbooks.Open(""" & threadFileName & """)" & vbCrLf s = s & "objExcel.Application.Visible = False" & vbCrLf s = s & "objExcel.Application.Run ""Thread_" & maxThreads & "_" & threadNr & ".xls!RunVBAMultithread"" ," & threadNr & "," & maxThreads & "," & divTabSize & ",""" & ActiveWorkbook.Name & """" & vbCrLf s = s & "objExcel.ActiveWorkbook.Close" & vbCrLf s = s & "objExcel.Application.Quit" '---Save the VBscript file--- sFileName = ActiveWorkbook.Path & "Thread_" & threadNr & ".vbs" Open sFileName For Output As #1 Print #1, s Close #1 '---Execute the VBscript file asynchronously--- Set wsh = VBA.CreateObject("WScript.Shell") wsh.Run """" & sFileName & """" Set wsh = Nothing End Sub Public Sub RunVBAMultithread(threadNr As Long, maxThreads As Long, divTabSize As Long, workbookName As String) 'A simple division algorithm (a multiplication table would quickly cause overflow) Dim i As Long, j As Long, x As Double, startTimeS As Date, endTimeS As Date startTimeS = Timer '---Compute partition of the division table--- For i = CInt(CDbl(divTabSize) * (threadNr - 1) / maxThreads + 1) To CInt(CDbl(divTabSize) * threadNr / maxThreads) For j = 1 To divTabSize x = CDbl(i) / j Next Next endTimeS = Timer elapsed = "" & Format(endTimeS - startTimeS, "0.00") Dim oXL Set oXL = GetObject(, "Excel.Application") oXL.Workbooks(workbookName).Sheets("Status").Range("A" & (threadNr + 1)) = elapsed Set oXL = Nothing End Sub '---------Join threads - wait until all finish and save elapsed time to worksheet---------- Sub CheckIfFinishedVBA(maxThreads As Long) Dim endTime As Double, elapsed As String Do Until False DoEvents If Range("A2").Value <> "" Then If Range("A2:A" & Range("A1").End(xlDown).Row).Count = maxThreads Then endTime = Timer elapsed = "" & Format(endTime - startTime, "0.00") Range("I1").Offset(maxThreads).Value = elapsed Exit Sub End If End If Sleep 50 Loop End Sub
Now what do the various procedures do?:
- VBAMultithread – the main thread which creates all the worker threads
- CreateVBAThread – creates a single thread. Copies the current workbook and creates a VBscript which is executed via Windows Shell. This script will run the RunVBAMultithread macro
- RunVBAMultithread – the worker macro which does the computations: which calculates a partition of the division table and save the elapsed time to the Master Excel Workbook
- CheckIfFinishedVBA – this macro will execute once all worker threads have been created. It will wait (Sleep and check in loop) until all worker threads have finished in order to evaluate the total execution time of the algorithm
Performance
Please see this post on how this method of multithreading in VBA compares to other available approaches:
EXCEL: Multithreading – VBA vs. VBscript vs. C#.NET
Download
You can download the source code here:
More links on Mulithreading VBA
Check out the VBA Mulithreading Tool (the easiest way to add multithreading to Excel macro):
EXCEL: VBA Multithreading Tool
Want to learn how to add multithreading using C#.NET?
EXCEL: Multithreading VBA using C#.NET
Want to learn how to add multithreading using VBscript?
EXCEL: Multithreading VBA using VBscript
Want to see how all multithreading approaches compare?
EXCEL: Multithreading – VBA vs. VBscript vs. C#.NET