Multi-threading in VBA

Qualcuno qui sa come ottenere VBA per eseguire più thread? Sto usando Excel.

Non può essere fatto in modo nativo con VBA. VBA è costruito in un appartamento a thread singolo. L’unico modo per ottenere più thread è creare una DLL in qualcosa di diverso da VBA che abbia un’interfaccia COM e chiamarla da VBA.

INFO: descrizioni e funzionamento dei modelli di threading OLE

Come probabilmente hai imparato, VBA non supporta nativamente il multithreading ma. Esistono 3 metodi per ottenere il multithreading:

  1. COM / DLL – ad es. C # e la class Parallel da eseguire in thread separati
  2. Utilizzando i thread di lavoro VBscript , esegui il codice VBA in thread VBscript separati
  3. Utilizzando i thread di lavoro VBA eseguiti ad esempio tramite VBscript , copia la cartella di lavoro di Excel ed esegui la tua macro in parallelo.

Ho confrontato tutti gli approcci di thread qui: http://analystcave.com/excel-multithreading-vba-vs-vbscript-vs-c-net/

Considerando l’approccio n. 3 ho anche creato uno strumento multithreading VBA che consente di aggiungere facilmente il multithreading a VBA: http://analystcave.com/excel-vba-multithreading-tool/

Vedi gli esempi qui sotto:

Multithreading a For Loop

Sub RunForVBA(workbookName As String, seqFrom As Long, seqTo As Long) For i = seqFrom To seqTo x = seqFrom / seqTo Next i End Sub Sub RunForVBAMultiThread() Dim parallelClass As Parallel Set parallelClass = New Parallel parallelClass.SetThreads 4 Call parallelClass.ParallelFor("RunForVBA", 1, 1000) End Sub 

Esegui una macro di Excel in modo asincrono

 Sub RunAsyncVBA(workbookName As String, seqFrom As Long, seqTo As Long) For i = seqFrom To seqTo x = seqFrom / seqTo Next i End Sub Sub RunForVBAAndWait() Dim parallelClass As Parallel Set parallelClass = New Parallel Call parallelClass.ParallelAsyncInvoke("RunAsyncVBA", ActiveWorkbook.Name, 1, 1000) 'Do other operations here '.... parallelClass.AsyncThreadJoin End Sub 

Stavo cercando qualcosa di simile e la risposta ufficiale è no. Tuttavia, sono stato in grado di trovare un concetto interessante di Daniel su ExcelHero.com.

Fondamentalmente, è necessario creare vbscripts di lavoro per eseguire le varie cose che si desidera e farlo riportare in excel. Per quello che sto facendo, recuperando i dati HTML da vari siti Web, funziona alla grande!

Guarda:

http://www.excelhero.com/blog/2010/05/multi-threaded-vba.html

Sto aggiungendo questa risposta poiché i programmatori che arrivano a VBA da linguaggi più moderni e cercano Stack Overflow per il multithreading in VBA potrebbero non essere a conoscenza di un paio di approcci VBA nativi che a volte aiutano a compensare la mancanza del vero multithreading di VBA.

Se la motivazione del multithreading è quella di avere un’interfaccia utente più retriggers che non si blocchi quando il codice a esecuzione prolungata è in esecuzione, VBA ha un paio di soluzioni low-tech che spesso funzionano nella pratica:

1) Le maschere utente possono essere visualizzate in modo non modale, il che consente all’utente di interagire con Excel mentre il modulo è aperto. Questo può essere specificato in fase di runtime impostando la proprietà ShowModal di Userform su false o può essere eseguita dynamicmente come dai carichi inserendo la riga

 UserForm1.Show vbModeless 

nell’evento di inizializzazione del modulo utente.

2) La dichiarazione DoEvents. Ciò fa sì che VBA ceda il controllo al sistema operativo per eseguire qualsiasi evento nella coda degli eventi, inclusi gli eventi generati da Excel. Un tipico caso d’uso è l’aggiornamento di un grafico mentre il codice è in esecuzione. Senza DoEvents il grafico non verrà ridipinto fino a quando non viene eseguita la macro, ma con Doevents è ansible creare grafici animati. Una variante di questa idea è il trucco comune di creare un indicatore di progresso. In un ciclo che deve eseguire 10.000.000 di volte (e controllato dall’indice di loop i ) puoi avere una sezione di codice come:

 If i Mod 10000 = 0 Then UpdateProgressBar(i) 'code to update progress bar display DoEvents End If 

Niente di tutto questo è il multithreading, ma in alcuni casi potrebbe essere un kludge adeguato.

So che la domanda specifica Excel, ma poiché la stessa domanda per Access è stata contrassegnata come duplicata, quindi inserirò qui la mia risposta. Il principio è semplice: apri una nuova applicazione di Access, quindi apri un modulo con un timer all’interno di quell’applicazione, invia la funzione / sub che vuoi eseguire a quel modulo, esegui l’attività se il timer colpisce, e chiudi l’applicazione una volta che l’esecuzione ha finito. Ciò consente a VBA di lavorare con tabelle e query dal database. Nota: genererà errori se hai bloccato il database esclusivamente.

Questo è tutto VBA (al contrario di altre risposte)

La funzione che esegue un sub / funzione in modo asincrono

 Public Sub RunFunctionAsync(FunctionName As String) Dim A As Access.Application Set A = New Access.Application A.OpenCurrentDatabase Application.CurrentProject.FullName A.DoCmd.OpenForm "MultithreadingEngine" With A.Forms("MultiThreadingEngine") .TimerInterval = 10 .AddToTaskCollection (FunctionName) End With End Sub 

Il modulo del modulo richiesto per raggiungere questo objective

(nome del form = MultiThreadingEngine, non ha alcun controllo o proprietà impostate)

 Public TaskCollection As Collection Public Sub AddToTaskCollection(str As String) If TaskCollection Is Nothing Then Set TaskCollection = New Collection End If TaskCollection.Add str End Sub Private Sub Form_Timer() If Not TaskCollection Is Nothing Then If TaskCollection.Count <> 0 Then Dim CollectionItem As Variant For Each CollectionItem In TaskCollection Run CollectionItem Next CollectionItem End If End If Application.Quit End Sub 

L’implementazione del supporto per i parametri dovrebbe essere abbastanza semplice, tuttavia restituire i valori è difficile.

 Sub MultiProcessing_Principle() Dim k As Long, j As Long k = Environ("NUMBER_OF_PROCESSORS") For j = 1 To k Shellm "msaccess", "C:\Autoexec.mdb" Next DoCmd.Quit End Sub Private Sub Shellm(a As String, b As String) ' Shell modificirani Const sn As String = """" Const r As String = """ """ Shell sn & a & r & b & sn, vbMinimizedNoFocus End Sub 

Come già detto, VBA non supporta il multithreading.

Ma non è necessario utilizzare C # o vbScript per avviare altri thread di lavoro VBA.

Uso VBA per creare thread di lavoro VBA .

Prima copia la cartella di lavoro Makro per ogni thread che vuoi avviare.

Quindi è ansible avviare nuove istanze di Excel (in esecuzione in un’altra discussione) semplicemente creando un’istanza di Excel.Application (per evitare errori, è necessario impostare la nuova applicazione su visibile).

Per eseguire effettivamente qualche attività in un altro thread, posso quindi avviare un makro nell’altra applicazione con i parametri della cartella di lavoro principale.

Per tornare al thread della cartella di lavoro principale senza attendere, semplicemente utilizzo Application.OnTime nel thread worker (dove ne ho bisogno).

Come semaforo uso semplicemente una collezione condivisa con tutti i thread. Per i callback passare la cartella di lavoro principale al thread di lavoro. Qui è ansible riutilizzare la funzione runMakroInOtherInstance per avviare una richiamata.

 'Create new thread and return reference to workbook of worker thread Public Function openNewInstance(ByVal fileName As String, Optional ByVal openVisible As Boolean = True) As Workbook Dim newApp As New Excel.Application ThisWorkbook.SaveCopyAs ThisWorkbook.Path & "\" & fileName If openVisible Then newApp.Visible = True Set openNewInstance = newApp.Workbooks.Open(ThisWorkbook.Path & "\" & fileName, False, False) End Function 'Start macro in other instance and wait for return (OnTime used in target macro) Public Sub runMakroInOtherInstance(ByRef otherWkb As Workbook, ByVal strMakro As String, ParamArray var() As Variant) Dim makroName As String makroName = "'" & otherWkb.Name & "'!" & strMakro Select Case UBound(var) Case -1: otherWkb.Application.Run makroName Case 0: otherWkb.Application.Run makroName, var(0) Case 1: otherWkb.Application.Run makroName, var(0), var(1) Case 2: otherWkb.Application.Run makroName, var(0), var(1), var(2) Case 3: otherWkb.Application.Run makroName, var(0), var(1), var(2), var(3) Case 4: otherWkb.Application.Run makroName, var(0), var(1), var(2), var(3), var(4) Case 5: otherWkb.Application.Run makroName, var(0), var(1), var(2), var(3), var(4), var(5) End Select End Sub Public Sub SYNCH_OR_WAIT() On Error Resume Next While masterBlocked.Count > 0 DoEvents Wend masterBlocked.Add "BLOCKED", ThisWorkbook.FullName End Sub Public Sub SYNCH_RELEASE() On Error Resume Next masterBlocked.Remove ThisWorkbook.FullName End Sub Sub runTaskParallel() ... Dim controllerWkb As Workbook Set controllerWkb = openNewInstance("controller.xlsm") runMakroInOtherInstance controllerWkb, "CONTROLLER_LIST_FILES", ThisWorkbook, rootFold, masterBlocked ... End Sub