Multi-threading en VBA

est-ce que quelqu'un ici sait comment faire tourner plusieurs threads avec VBA? Je suis à l'aide d'Excel.

49
demandé sur Gaffi 2011-04-19 23:26:03

7 réponses

ne peut pas être fait nativement avec VBA. VBA est construit dans un appartement simple-fileté. La seule façon d'obtenir plusieurs threads est de construire une DLL dans quelque chose d'autre que VBA qui a une interface COM et l'appeler de VBA.

INFO: Descriptions et fonctionnement des modèles de filetage OLE

54
répondu Thomas 2013-07-23 15:13:27

comme vous avez probablement appris VBA ne supporte pas nativement multithreading mais. Il y a 3 méthodes pour réaliser le multithreading:

  1. COM /dlls - p.ex. C# et la classe parallèle à exécuter dans des fils séparés
  2. utilisant les fils de travail VBscript - exécutez votre code VBA dans des fils VBScript séparés
  3. utilisant des fils de travail VBA exécutés, p.ex. via VBscript - Copiez le classeur Excel et exécutez votre macro en parallèle.

j'ai comparé toutes les approches fil ici: http://analystcave.com/excel-multithreading-vba-vs-vbscript-vs-c-net/

compte tenu de l'approche #3 j'ai également fait un outil de Multithreading VBA qui vous permet d'ajouter facilement multithreading à VBA: http://analystcave.com/excel-vba-multithreading-tool/

Voir les exemples ci-dessous:

Multithreading d'une Boucle For

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

Exécuter une macro Excel en mode asynchrone

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
18
répondu AnalystCave.com 2015-06-18 13:36:58

je cherchais quelque chose de similaire et la réponse officielle est non. Cependant, J'ai pu trouver un concept intéressant par Daniel à ExcelHero.com.

fondamentalement, vous avez besoin de créer des vbscripteurs d'ouvrier pour exécuter les diverses choses que vous voulez et avoir le rapport de retour à excel. Pour ce que je fais, extraire des données HTML de divers sites web, il fonctionne très bien!

regardez:

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

13
répondu stanleykylee 2011-11-18 11:00:00

j'ajoute cette réponse car les programmeurs qui viennent à VBA depuis des langues plus modernes et qui cherchent le débordement de la pile pour le multithreading en VBA pourraient ne pas être au courant de quelques approches natives de VBA qui aident parfois à compenser le manque de VBA de véritable multithreading.

si la motivation de la multithreading est d'avoir une interface utilisateur plus réactive qui ne tient pas lorsque le code à long terme est l'exécution, VBA a un couple de solutions de faible technologie qui fonctionnent souvent dans pratique:

1) Les Userforms peuvent être créés pour afficher modellessly - ce qui permet à L'utilisateur D'interagir avec Excel pendant que le formulaire est ouvert. Cela peut être spécifié à l'exécution en définissant la propriété ShowModal de L'Userform à false ou peut être fait dynamiquement comme le de charges en mettant la ligne

UserForm1.Show vbModeless

dans l'événement initialize du formulaire utilisateur.

2) la déclaration de DoEvents. Cela amène VBA à céder le contrôle à L'OS pour exécuter n'importe quel événements dans la file d'attente des événements - y compris les événements générés par Excel. Un cas d'utilisation typique est la mise à jour d'un graphique pendant que le code est exécuté. Sans DoEvents, le graphique ne sera repeint qu'après l'exécution de la macro, mais avec Doevents vous pouvez créer des graphiques animés. Une variante de cette idée est le tour commun de créer un compteur de progrès. Dans une boucle qui doit exécuter 10 000 000 de fois (et contrôlée par l'index de boucle i ) vous pouvez avoir une section de code comme:

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

rien de tout cela n'est en plusieurs lectures -- mais cela pourrait être un kludge adéquat dans certains cas.

5
répondu John Coleman 2015-07-04 15:00:53

je sais que la question spécifie Excel, mais puisque la même question pour L'accès a été marquée comme dupliquer, donc je vais poster ma réponse ici. Le principe est simple: ouvrir une nouvelle application D'accès, puis ouvrir un formulaire avec un timer à l'intérieur de cette application, envoyer la fonction/Sous que vous voulez exécuter à ce formulaire, exécuter la tâche si le timer frappe, et quitter l'application Une fois l'exécution est terminée. Cela permet au VBA de travailler avec des tables et des requêtes de votre base de données. Remarque: il va le jeter erreurs si vous avez exclusivement verrouillé la base de données.

C'est tout VBA (par opposition à d'autres réponses)

La fonction qui exécute un sub/function en mode asynchrone

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

le module du formulaire requis pour obtenir ce

(nom de la forme = MultiThreadingEngine, n'a pas de contrôles ou de propriétés)

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

La mise en œuvre du support des paramètres devrait être assez facile, mais le retour des valeurs est difficile.

1
répondu Erik von Asmuth 2017-04-13 09:34:22
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
répondu Darijo11 2018-04-29 11:21:31

comme dit plus haut, VBA ne supporte pas le Multithreading.

mais vous n'avez pas besoin d'utiliser C# ou vbScript pour démarrer d'autres VBA worker threads.

j'utilise VBA pour créer VBA worker threads .

copiez D'abord le classeur makro pour chaque thread que vous voulez lancer.

alors vous pouvez lancer de nouvelles Instances Excel (tournant dans un autre Thread) simplement en créant un instance D'Excel.Application (pour éviter les erreurs, je dois définir la nouvelle application à visible).

pour réellement exécuter une tâche dans un autre thread, je peux alors lancer un makro dans l'autre application avec les paramètres du cahier de travail principal.

pour retourner au thread du cahier de travail principal sans attendre, j'utilise simplement Application.OnTime dans le thread worker (là où j'en ai besoin).

comme sémaphore j'utilise simplement une collection qui est partagée avec tous les threads. Pour les callbacks passer le cahier de travail principal au thread worker. Là, la fonction runMakroInOtherInstance peut être réutilisée pour lancer un rappel.

'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
0
répondu Stiebl 2018-06-14 08:00:52