web-dev-qa-db-fra.com

Multi-threading dans VBA

Est-ce que quelqu'un ici sait comment amener VBA à exécuter plusieurs threads? J'utilise Excel.

53

Ne peut être fait en mode natif avec VBA. VBA est construit dans un appartement à un seul thread. La seule façon d'obtenir plusieurs threads consiste à créer une DLL dans un environnement autre que VBA doté d'une interface COM et à l'appeler à partir de VBA.

INFO: descriptions et fonctionnement des modèles de threading OLE

55
Thomas

Comme vous l'avez probablement appris, VBA ne prend pas nativement en charge le multithreading. Il existe 3 méthodes pour réaliser le multithreading:

  1. COM/dlls - par exemple C # et la classe Parallel à exécuter dans des threads séparés
  2. Utilisation de threads de travail VBscript - exécutez votre code VBA dans des threads VBscript distincts
  3. Utilisation de threads de travail VBA exécutés, par exemple. via VBscript - copiez le classeur Excel et exécutez votre macro en parallèle.

J'ai comparé toutes les approches de thread ici: http://analystcave.com/Excel-multithreading-vba-vs-vbscript-vs-c-net/

Considérant l'approche n ° 3, j'ai également créé un outil de multithreading VBA qui vous permet d'ajouter facilement du 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 de manière 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
19
AnalystCave.com

Je cherchais quelque chose de similaire et la réponse officielle est non. Cependant, j'ai pu trouver un concept intéressant de Daniel sur ExcelHero.com.

Fondamentalement, vous devez créer vbscripts pour exécuter les différentes tâches souhaitées et faire rapport à Excel. Pour ce que je fais, récupérer des données HTML à partir de divers sites Web, cela fonctionne très bien!

Regarde:

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

13
stanleykylee

J'ajoute cette réponse, car les programmeurs qui utilisent VBA depuis des langues plus modernes et qui recherchent le multithreading dans VBA avec Stack Overflow peuvent ignorer quelques approches VBA natives qui permettent parfois de compenser le manque de véritable multithreading par VBA.

Si le multithreading est motivé par la création d'une interface utilisateur plus réactive qui ne bloque pas lorsque du code à exécution longue est en cours d'exécution, VBA propose quelques solutions peu techniques qui fonctionnent souvent dans la pratique:

1) Les formulaires utilisateur peuvent être conçus pour être affichés sans modalité, 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 Userform sur false ou peut être effectué de manière dynamique en tant que charge depuis en mettant la ligne.

UserForm1.Show vbModeless

dans l'événement d'initialisation du formulaire utilisateur.

2) la déclaration DoEvents. Cela oblige VBA à céder le contrôle au système d'exploitation pour exécuter tous les événements de la file d'attente des événements, y compris les événements générés par Excel. Un cas d'utilisation typique consiste à mettre à jour un graphique pendant l'exécution du code. Sans DoEvents, le graphique ne sera pas repeint avant que la macro ne soit exécutée, mais avec Doevents, vous pouvez créer des graphiques animés. Une variante de cette idée est l'astuce courante pour créer un indicateur de progression. Dans une boucle qui doit être exécutée 10 000 000 fois (et contrôlée par l'index de la 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 multithreading - mais cela pourrait être un kludge adéquat dans certains cas.

6
John Coleman

Je sais que la question spécifie Excel, mais puisque la même question pour Access a été marquée comme duplicata, je vais donc poster ma réponse ici . Le principe est simple: ouvrez une nouvelle application Access, puis ouvrez un formulaire avec une minuterie. application, envoyez la fonction/sous-fonction que vous souhaitez exécuter à ce formulaire, exécutez la tâche si le chronomètre se déclenche et quittez l'application une fois l'exécution terminée. Cela permet à VBA de travailler avec des tables et des requêtes de votre base de données. Remarque: des erreurs seront renvoyées si vous avez exclusivement verrouillé la base de données.

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

La fonction qui exécute une sous/fonction de manière 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 y parvenir  

(nom du formulaire = MultiThreadingEngine, aucun contrôle ni propriétés ne sont définis)

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 prise en charge des paramètres doit être assez simple, mais il est difficile de renvoyer des valeurs.

2
Erik A

Comme indiqué précédemment, VBA ne prend pas en charge le multithreading.

Mais vous n'avez pas besoin d'utiliser C # ou vbScript pour démarrer d'autres threads de travail VBA.

J'utilise VBA pour créer des threads de travail VBA .

Commencez par copier le classeur makro pour chaque thread que vous souhaitez démarrer.

Ensuite, vous pouvez démarrer de nouvelles instances Excel (s'exécutant dans un autre thread) en créant simplement une instance d'Excel.Application (pour éviter les erreurs, je dois définir la nouvelle application sur visible).

Pour exécuter une tâche dans un autre thread, je peux alors démarrer un makro dans une autre application avec des paramètres du classeur principal.

Pour revenir au thread de classeur principal sans attendre, utilisez simplement Application.OnTime dans le thread de travail (là où j'en ai besoin).

En tant que sémaphore, j'utilise simplement une collection partagée avec tous les threads . Pour les rappels, transmettez le classeur maître au thread de travail. 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
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