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

49

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

Теги:
excel-vba
excel
multithreading

7 ответов

52
Лучший ответ

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

INFO: описания и разработки моделей потоков OLE

  • 10
    Хорошо, это не выглядит хорошо для меня.
  • 0
    @ blog.tkacprow.pl - Ни один из этих методов изначально не является VBA. Все они полагаются на что-то вне VBA.
Показать ещё 6 комментариев
18

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

  • COM/dll - например. С# и класс Parallel для запуска в отдельных потоках
  • Использование рабочих потоков VBscript. Запустите код VBA в отдельных потоках VBscript.
  • Использование рабочих потоков VBA, выполненных, например. через VBscript - скопируйте книгу Excel и параллельно выполняйте макрос.

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

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

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

Многопоточность a для цикла

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
  • 1
    Являются ли 1 и 1000 обязательными в моем случае, у меня есть ряд больших ячеек, которые нужно обработать, насколько доза подходит для этой многопоточности, пожалуйста?
  • 0
    1 и 1000 являются просто параметрами примера функции RunForVBA . Поток ParallelAsync не должен иметь эти параметры. Вы также можете оставить только параметр workbookName. Загрузите инструмент и посмотрите на примеры внутри: analystcave.com/excel-vba-multithreading-tool
Показать ещё 1 комментарий
13

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

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

Посмотрите:

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

  • 0
    Вы можете иметь сотни VBScript-программ, извлекающих данные из Интернета, если VBA является однопоточным, он может обрабатывать только один результат за раз.
  • 2
    @JimmyPena Но если сценарий выполняет всю работу, а возвращение результата требует минимальной обработки, тогда это все еще эффективно в сокращении времени обработки (хотя да, это не истинная многопоточность). Вы ставите в очередь ряд очень маленьких действий вместо очень больших действий.
Показать ещё 2 комментария
5

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

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

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

UserForm1.Show vbModeless

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

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

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

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

  • 0
    Благодарю. К сожалению, это не совсем соответствует первоначальному запросу, связанному с многопоточностью из VBA. Использование этих методов может помочь предотвратить блокировку графического интерфейса, но в конечном итоге увеличивает время выполнения, поэтому многие программисты предпочитают запускать определенные итеративные задачи в многопоточном режиме, где это технически возможно.
  • 5
    Я согласен - я просто подумал, что не помешает выделить пару чистых VBA-кладжей, которые иногда помогают
Показать ещё 1 комментарий
1

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

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

Функция, выполняющая асинхронно под/функцию

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

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

0

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

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

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

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

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

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

Чтобы вернуться в основной поток рабочей книги без ожидания, я просто использую Application.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
0
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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню