Многопоточность в VBA

кто-нибудь здесь знает, как заставить VBA запускать несколько потоков? Я использую Excel.

7 ответов


не может быть сделано изначально с VBA. VBA построен в однопоточной квартире. Единственный способ получить несколько потоков-построить DLL в чем-то другом, чем VBA, который имеет COM-интерфейс и вызвать его из VBA.

INFO: описания и работы моделей Ole Threading


Как вы, вероятно, узнали, VBA изначально не поддерживает многопоточность, но. Есть 3 метода для достижения многопоточности:

  1. COM / DLL - например, C# и параллельный класс для запуска в отдельных потоках
  2. использование рабочих потоков VBscript - запустите код VBA в отдельных потоках VBscript
  3. используя рабочие потоки VBA, выполняемые, например, через VBscript - скопируйте книгу Excel и запустите макрос в параллельный.

Я сравнил все подходы к потоку здесь:http://analystcave.com/excel-multithreading-vba-vs-vbscript-vs-c-net/

учитывая подход #3 я также сделал инструмент многопоточности VBA, который позволяет легко добавлять многопоточность в VBA:http://analystcave.com/excel-vba-multithreading-tool/

см. примеры ниже:

многопоточность для Петля

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

запустите макрос Excel асинхронно

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

Я искал что-то подобное и официального ответа нет. Тем не менее, я смог найти интересную концепцию Даниэля в ExcelHero.com.

в основном, вам нужно создать рабочие vbscripts для выполнения различных вещей, которые вы хотите, и иметь его отчет обратно в excel. Для того, что я делаю, извлекая данные HTML с различных веб-сайтов, он отлично работает!

принять смотри:

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


Я добавляю этот ответ, так как программисты, приходящие в VBA с более современных языков, и поиск переполнения стека для многопоточности в VBA, могут не знать о нескольких собственных подходах VBA, которые иногда помогают компенсировать отсутствие истинной многопоточности VBA.

если мотивация многопоточности состоит в том, чтобы иметь более отзывчивый пользовательский интерфейс, который не зависает при выполнении долгосрочного кода, у VBA есть несколько низкотехнологичных решений, которые часто работают в практика:

1) Userforms можно сделать для показа modelessly-которое позволяет потребителю взаимодействовать с Excel пока форма открыта. Это можно указать во время выполнения, установив свойство Showmodal Userform в false или можно сделать динамически, как от нагрузок, поставив строку

UserForm1.Show vbModeless

в инициализации события пользовательской формы.

2) оператор DoEvents. Это заставляет VBA передавать управление ОС для выполнения любых событий в очереди событий - включая события, генерируемые Excel. Типичным вариантом использования является обновление диаграммы во время выполнения кода. Без DoEvents диаграмма не будет перекрашена до тех пор, пока не будет запущен макрос, но с Doevents вы можете создавать анимированные диаграммы. Вариация этой идеи является общим трюком создания индикатора прогресса. В цикле, который должен выполняться 10,000,000 раз (и управляется индексом цикла Я ) можно в разделе код:

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

все это многопоточность - но в некоторых случаях это может быть адекватный kludge.


Я знаю, что вопрос указывает Excel, но так как тот же вопрос для доступа был отмечен как дубликат, поэтому я опубликую свой ответ здесь. Принцип прост: откройте новое приложение Access, затем откройте форму с таймером внутри этого приложения, отправьте функцию/sub, которую вы хотите выполнить, в эту форму, выполните задачу, если таймер попадет, и закройте приложение после завершения выполнения. Это позволяет VBA работать с таблицами и запросами из вашей базы данных. Примечание: он будет бросать ошибки если вы заблокировали базу данных.

Это все VBA (в отличие от других ответов)

функция, которая запускает sub/function асинхронно

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

модуль формы, необходимой для достижения этого

(имя формы = MultiThreadingEngine, не имеет никаких элементов управления или свойств)

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

реализация поддержки параметров должна быть достаточно простой, возврат значений затруднен, однако.


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

Как уже было сказано, VBA не поддерживает многопоточность.

а вы не нужно использовать C# или vbScript для запуска других рабочих потоков VBA.

Я использую VBA для создания рабочих потоков VBA.

сначала скопируйте книгу makro для каждого потока, который вы хотите запустить.

затем вы можете запустить новые экземпляры Excel (запущенные в другом потоке), просто создав экземпляр Excel.Приложения (чтобы избежать ошибок, я должен установить новое приложение для видимого).

чтобы фактически запустить некоторую задачу в другом потоке, я могу запустить makro в другом приложении с параметрами из главной книги.

чтобы вернуться в основной поток книги, не дожидаясь, я просто использую приложение.OnTime в рабочем потоке (где мне это нужно).

в качестве семафора я просто использую коллекцию, которая совместно используется со всеми потоками. Для обратных вызовов передайте главную книгу в рабочий поток. Там функцию runMakroInOtherInstance можно повторно использовать для запуска обратного вызова.

'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