"Correct to say there is no multithreading in VBA however, you can very easily create your multithreading functionality in a c# dll and expose the interface via Interops for VBA to consume. Then all you need to do is to pass in the data into the function and walla, you have multithreading. Interops is a very powerful feature and VBA fully supports it."
For the sake of completeness, I will present one possible program to implement such multithreading scheme for Excel, by using Parallel class for the actual multi-threading part and Excel-DNA for interfacing the program back to Excel (using Excel only as Input-Output platform). Excel interfacing part used in this post, has already been completely chewed within this post. Note, that by using instructions given in here or here, one can relatively easy interface this multithreading program back to VBA, instead of Excel.
The program
This unsurprising program is simply processing a task (simulate N amount of normally distributed random numbers) shared for M processors. Amount of simulated numbers and number of processors used is decided by the client (Excel). Finally, the program will print the aggregated time consumed for this task back to Excel. Needless to say, the effect of multithreading can be demonstrated by changing the number of processors in Excel worksheet.
Copy-Paste the following program presented below into a new C# project. Also, follow carefully all instructions given in this post.
Thanks for reading this blog.
-Mike
// ExcelInterface.cs using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; using System.Windows.Forms; using ExcelDna.Integration; namespace ExcelInterface { // type definition, delegate for exporting data from processor to queue using Processor = Action<int, DataMessenger>; public delegate void DataMessenger(List<double> result); public static class ExcelInterface { // method available in excel public static void execute() { try { // create Excel application object dynamic Excel = ExcelDnaUtil.Application; // // get parameters from worksheet named ranges int n = (int)Excel.Range["_n"].Value2; int nProcessors = (int)Excel.Range["_nProcessors"].Value2; int nPerProcessor = n / nProcessors; // create data thread-safe queue and random processors RandomQueue messageQueue = new RandomQueue(); List<Processor> processors = new List<Processor>(); for (int i = 0; i < nProcessors; ++i) { processors.Add(Factory.CreateProcessor()); } // host timer, execute all processors in parallel Stopwatch timer = new Stopwatch(); timer.Start(); Parallel.For(0, nProcessors, i => { processors[i].Invoke(nPerProcessor, messageQueue.Enqueue); }); timer.Stop(); // // print processing time to excel double timeElapsed = timer.Elapsed.TotalSeconds; Excel.Range["_timer"] = timeElapsed; } catch (Exception e) { MessageBox.Show(e.Message); } } } } // RandomQueue.cs using System; using System.Collections.Concurrent; using System.Collections.Generic; namespace ExcelInterface { // wrapper class for ConcurrentQueue data structure public class RandomQueue { private ConcurrentQueue<List<double>> randomQueue; public RandomQueue() { randomQueue = new ConcurrentQueue<List<double>>(); } // add public void Enqueue(List<double> data) { randomQueue.Enqueue(data); } // remove public List<double> Dequeue() { List<double> data = null; bool hasValue = false; while (!hasValue) hasValue = randomQueue.TryDequeue(out data); return data; } } }
// Factory.cs using System; using System.Collections.Generic; namespace ExcelInterface { // type definition using Processor = Action<int, DataMessenger>; public static class Factory { // factory method for creating processor for random generator public static Processor CreateProcessor() { // // create standard normal variable generator // using action delegate and lambda expression Processor process = (int n, DataMessenger dataMessenger) => { Random generator = new Random(Guid.NewGuid().GetHashCode()); List<double> normalRandomList = new List<double>(); // loop through n for (int i = 0; i < n; ++i) { double uniformRandomSum = 0.0; // create standard normal random variable using CLT for (int j = 0; j < 12; ++j) { double uniformRandom = 0.0; while (true) { uniformRandom = generator.NextDouble(); if (uniformRandom > 0.0) break; } uniformRandomSum += uniformRandom; } normalRandomList.Add(uniformRandomSum - 6.0); } // send list of normal random variables to queue dataMessenger(normalRandomList); }; return process; } } }