web-dev-qa-db-de.com

Multithreading in VBA

Weiß jemand hier, wie man VBA dazu bringt, mehrere Threads auszuführen? Ich benutze Excel.

53

Kann nicht mit VBA nativ ausgeführt werden. VBA ist in einer Single-Threaded-Wohnung gebaut. Die einzige Möglichkeit, mehrere Threads zu erhalten, besteht darin, eine DLL in etwas anderem als VBA mit einer COM-Schnittstelle zu erstellen und von VBA aus aufzurufen.

INFO: Beschreibungen und Funktionsweise von OLE - Threading-Modellen

55
Thomas

Wie Sie wahrscheinlich gelernt haben, unterstützt VBA nicht nativ Multithreading, sondern. Es gibt drei Methoden, um Multithreading zu erreichen:

  1. COM/dlls - z. C # und die Parallel-Klasse werden in separaten Threads ausgeführt
  2. Using VBscript-Arbeitsthreads - Führen Sie Ihren VBA-Code in separaten VBscript-Threads aus
  3. Verwenden von VBA-Arbeitsthreads, die z. via VBscript - kopieren Sie die Excel-Arbeitsmappe und führen Sie Ihr Makro parallel aus.

Ich habe alle Thread-Ansätze hier verglichen: http://analystcave.com/Excel-multithreading-vba-vs-vbscript-vs-c-net/

In Anbetracht von Ansatz Nr. 3 habe ich auch ein VBA-Multithreading-Tool erstellt, mit dem Sie VBA problemlos mit Multithreading versehen können: http://analystcave.com/Excel-vba-multithreading-tool/

Siehe die Beispiele unten: 

Multithreading einer For-Schleife

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

Ein Excel-Makro asynchron ausführen

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
19
AnalystCave.com

Ich habe nach etwas Ähnlichem gesucht und die offizielle Antwort lautet Nein. Ich konnte jedoch ein interessantes Konzept von Daniel bei ExcelHero.com finden.

Grundsätzlich müssen Sie Worker-Vbscripts erstellen, um die verschiedenen gewünschten Aufgaben auszuführen und diese an Excel zurückzumelden. Für das, was ich tue, HTML-Daten von verschiedenen Websites abzurufen, funktioniert es hervorragend!

Schau mal:

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

13
stanleykylee

Ich füge diese Antwort hinzu, da Programmierer aus moderneren Sprachen zu VBA kommen und Stack Overflow für Multithreading in VBA suchen. Möglicherweise sind einige native VBA-Ansätze nicht bekannt, die manchmal dazu beitragen, VBAs Mangel an echtem Multithreading auszugleichen.

Wenn die Motivation für Multithreading darin besteht, eine reaktionsschnellere Benutzeroberfläche zu haben, die nicht hängen bleibt, wenn lang andauernder Code ausgeführt wird, bietet VBA einige Low-Tech-Lösungen, die in der Praxis häufig funktionieren:

1) Benutzerformulare können modelllos angezeigt werden, sodass der Benutzer mit Excel interagieren kann, während das Formular geöffnet ist. Dies kann zur Laufzeit angegeben werden, indem die ShowModal-Eigenschaft des Userformats auf false festgelegt wird, oder dynamisch als from-Anweisungen durch Setzen der Zeile

UserForm1.Show vbModeless

im initialize-Ereignis des Benutzerformulars.

2) Die DoEvents-Anweisung. Dies bewirkt, dass VBA die Steuerung an das Betriebssystem überträgt, um Ereignisse in der Ereigniswarteschlange auszuführen, einschließlich der von Excel generierten Ereignisse. Ein typischer Anwendungsfall ist das Aktualisieren eines Diagramms, während der Code ausgeführt wird. Ohne DoEvents wird das Diagramm erst nach Ausführung des Makros neu gezeichnet. Mit Doevents können Sie jedoch animierte Diagramme erstellen. Eine Variante dieser Idee ist der übliche Trick beim Erstellen eines Fortschrittsmessers. In einer Schleife, die 10.000.000 Mal ausgeführt werden soll (und durch den Schleifenindex i gesteuert wird), können Sie einen Codeabschnitt wie folgt erstellen:

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

All dies ist kein Multithreading - in manchen Fällen ist dies jedoch ein ausreichender Fehler.

6
John Coleman

Ich weiß, dass die Frage Excel angibt, aber da dieselbe Frage für Access als doppelt markiert wurde, werde ich meine Antwort hier posten .. Das Prinzip ist einfach: Öffnen Sie eine neue Access-Anwendung und öffnen Sie dann ein Formular mit einem Timer Anwendung, senden Sie die Funktion/das Unterprogramm, das Sie ausführen möchten, an dieses Formular, führen Sie die Task aus, wenn der Timer einen Treffer ergibt, und beenden Sie die Anwendung, wenn die Ausführung abgeschlossen ist. Dadurch kann der VBA mit Tabellen und Abfragen aus Ihrer Datenbank arbeiten. Hinweis: Wenn Sie die Datenbank ausschließlich gesperrt haben, werden Fehler ausgegeben.

Dies ist alles VBA (im Gegensatz zu anderen Antworten)

Die Funktion, die eine UnterFunktion asynchron ausführt - /

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

Das Modul des Formulars, um dies zu erreichen  

(form name = MultiThreadingEngine, hat keine Steuerelemente oder Eigenschaften gesetzt)

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

Die Implementierung der Unterstützung für Parameter sollte einfach genug sein, die Rückgabe von Werten ist jedoch schwierig.

2
Erik A

Wie bereits erwähnt, unterstützt VBA kein Multithreading.

Sie müssen jedoch C # oder VbScript müssen nicht verwendet werden, um andere VBA-Arbeitsthreads zu starten.

Ich verwende VBA, um VBA-Arbeitsthreads zu erstellen.

Kopieren Sie zuerst die Makro-Arbeitsmappe für jeden Thread, den Sie starten möchten.

Dann können Sie neue Excel-Instanzen (in einem anderen Thread) starten, indem Sie einfach eine Instanz von Excel.Application erstellen (um Fehler zu vermeiden, muss die neue Anwendung sichtbar gemacht werden).

Um tatsächlich eine Aufgabe in einem anderen Thread auszuführen, kann ich in der anderen Anwendung ein Makro mit Parametern aus der Master-Arbeitsmappe starten.

Um zum Master-Workbook-Thread zurückzukehren, ohne zu warten, verwende ich einfach Application.OnTime im Worker-Thread (wo ich ihn brauche).

Als Semaphor verwende ich einfach eine Sammlung, die mit allen Threads gemeinsam genutzt wird. Für Callbacks übergeben Sie die Master-Arbeitsmappe an den Worker-Thread. Dort kann die runMakroInOtherInstance-Funktion erneut verwendet werden, um einen Rückruf zu starten.

'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
1
Stiebl
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
0
Darijo11