Multithreading VBA-Excel中的多线程处理

Multithreading VBA-Excel中的多线程处理,multithreading,vba,excel,Multithreading,Vba,Excel,如何在VBA中编写代码以获得多线程解析 我看过了,但它不起作用 我有10000个站点,每个站点在A列的一行中。我需要至少10个并发线程来解析标记之间的信息,在每个站点上使用index.php中的rel=“external”,然后将结果保存到B列的每一行。正如@Siddharth Rout在他的评论中指出的那样,答案是否定的。但要对此稍作扩展,即使是在后台运行并启用类似多线程功能的方法也不允许使用多线程 这方面的一个很好的例子是。它允许在将来的某个时间点运行一个过程 此方法允许用户继续编辑工作簿,

如何在VBA中编写代码以获得多线程解析

我看过了,但它不起作用


我有10000个站点,每个站点在A列的一行中。我需要至少10个并发线程来解析标记之间的信息
,在每个站点上使用index.php中的
rel=“external”
,然后将结果保存到B列的每一行。

正如@Siddharth Rout在他的评论中指出的那样,答案是否定的。但要对此稍作扩展,即使是在后台运行并启用类似多线程功能的方法也不允许使用多线程

这方面的一个很好的例子是。它允许在将来的某个时间点运行一个过程

此方法允许用户继续编辑工作簿,直到经过预设的时间量并调用该过程。乍一看,聪明地使用它可能会使多个代码片段同时运行。考虑下面的片段:

For a = 1 To 500000000
Next a
我机器上的下一个循环大约需要5秒钟才能完成。现在考虑这个问题:

Application.OnTime Now + TimeValue("00:00:1"), "ztest2"
For a = 1 To 500000000
Next a
这将在读取Application.OnTime语句一秒钟后调用“ztest2”。可以想象,由于For…Next循环需要5秒,.OnTime将在1秒后执行,因此可能会在For…Next循环中调用“ztest2”,即psuedo多线程


但事实并非如此。正如运行上述代码所示,Application.OnTime必须耐心等待,直到For…Next循环完成。

虽然您不能执行真正的多线程,即在不同的内核上同时并行运行线程,但您可以通过将多线程的操作排队来模拟多线程代码

例如: 每600毫秒(毫秒)运行一次subA,每200毫秒运行一次SubB,顺序如下: SubB,SubB,SubB,SubA,SubB,SubB,SubB,SubA,SubB,SubB,SubB

'Create a new class Tick_Timer to get access to NumTicks which counts ticks in
'milliseconds.
'While not used for this script, this class can also be used for a millisecond
'StartTimer/EndTimer which I included below.
'It can also be used to create a pause, similar to wait but in ms, that can
'allow other code to run while paused which I prefer over the sleep function.
'Sleep doesn't allow interruptions and hogs processor time. 
'The pause function would be placed in a module and works similar to the
'Queue Timer loop which I'll explain below.
Private StartTick As Long
Private Declare Function GetTickCount Lib "kernel32" () As Long

Public Function GetNumTicks() As Long
    GetNumTicks = GetTickCount
End Function

'Timer functions(not used in this script)
Public Sub StartTimer()
    StartTick = GetTickCount
End Sub

Public Function EndTimer() As Long
    EndTimer = (GetTickCount - StartTick)
End Function
在一个模块中,我声明了一些全局变量。虽然我知道很多人认为使用全局变量S是不好的做法。我总是为全局变量使用前缀,这样它们就不会与局部变量混淆

在这种情况下,全局变量比使用参数有优势,因为新线程可能会在队列中第一个计时器之前需要执行的任何时间被调用

全局变量可以在任何地方更改,以便动态更新队列。还考虑到几乎每个子例程都以某种方式使用队列,所以只需使用全局变量就更有意义了。
Public ST_TimerName As String 'Subroutine Name that is run as a new thread.

'Two strings are used to store the queue. 
'The first stores only the start times of each thread in tickcounts. 
'This allows me to sort the queue more easily.
'The second string (ST_TimerQ) contains TimerDelay:TimerName and is created at the
'same time as the sorted launch times so they are kept synchronous.
Public ST_EndTickQ As String  'queue string: trigger times in TickCounts.
Public ST_TimerQ As String    'queue string: TimerDelay:TimerName 

'New class that allows you to get the current Tick Count.
Public ST_Timer As Tick_Timer 'timer that accesses to Tick Count

Sub SetTimer(ByVal TimerName As String, ByVal TimerDelay As Long)
'Starts a new thread called TimerName which executes after TimerDelay(ms)
'TimerName: Name of subroutine that is to be activated.
'TimerDelay:
'-value for single execution after abs(-value) delay,
'+value Repeats TimerName with a period of TimerDelay.
'0 stops repeating TimerName.
    Dim EndTick As Long
    Dim TimerDat As String

    Set ST_Timer = New Tick_Timer
    EndTick = ST_Timer.GetNumTicks + Abs(TimerDelay)

    If TimerDelay = 0 Then
    'Stops TimerName
        RemoveFromQ TimerName
    Else
    'Insert to Queue, single or repeated is determined by +/-delay stored in TimerDat.
        TimerDat = TimerDelay & ":" & TimerName
        Call AddToQ(TimerDat, EndTick)
    End If
End Sub 'SetTimer

Sub SetTimerQLoop()
'All threads are continuously merged into an action queue with a sequential
'insertion sort.
'A simple loop containing only the DoEvents function(allows other VBA code to run) 
'loops until the the next thread in the queue needs to start.
'An outer loop runs through the queue until EOQ.
    Dim EndTick As Long
    Dim EOQ As Boolean

    On Error GoTo ErrHandler
    EOQ = False
    'SetTimer Queue Loop
    Do While Not (EOQ)
        'Delay Loop, DoEvents allows other vba scripts to run during delay.
        Do
            DoEvents
        Loop Until ST_Timer.GetNumTicks >= Val(ST_EndTickQ)

        Application.Run ST_TimerName

        If Val(ST_TimerQ) > 0 Then
        'Reinsert into queue threads with pos delay value.
            EndTick = Val(ST_EndTickQ) + Val(ST_TimerQ)
            TimerDat = Val(ST_TimerQ) & ":" & ST_TimerName
            Call AddToQ(TimerDat, EndTick)
        End If

        If ST_TimerQ = vbNullString Then
            EOQ = True
        Else
            GetNextQ
        End If
    Loop
Exit Sub
ErrHandler:
    'Break Key
End Sub 'SetTimerQLoop

Sub AddToQ(ByVal TimerDat As String, ByVal EndTick As Long)
    Dim EndTickArray() As String
    Dim TimerArray() As String
    Dim LastTickIndex As Integer
    Dim LastTimerIndex As Integer

    Dim PosDatDel As Integer
    Dim TimerDelay As Long
    Dim TimerName As String
    Dim QFirstTick As Long
    Dim QLastTick As Long

    PosDatDel = Len(TimerDat) - InStr(TimerDat, ":")
    TimerDelay = Val(TimerDat)
    TimerName = Right(TimerDat, PosDatDel)

    If ST_EndTickQ = vbNullString Then
    'First timer
        ST_TimerName = TimerName
        ST_EndTickQ = EndTick
        ST_TimerQ = TimerDat
        SetTimerQLoop
    ElseIf InStr(ST_EndTickQ, "|") = 0 Then
    'Second timer
        If EndTick < Val(ST_EndTickQ) Then
        'New timer is first of 2 in Q
            ST_TimerName = TimerName
            ST_EndTickQ = EndTick & "|" & ST_EndTickQ
            ST_TimerQ = TimerDat & "|" & ST_TimerQ
        Else
        'New timer is 2nd of 2 in Q
            ST_TimerName = TimerNameF(ST_TimerQ)
            ST_EndTickQ = ST_EndTickQ & "|" & EndTick
            ST_TimerQ = ST_TimerQ & "|" & TimerDat
        End If
    Else
    '3rd+ timer: split queue into an array to find new timers position in queue.
        TimerArray = Split(ST_TimerQ, "|")
        LastTimerIndex = UBound(TimerArray)
        EndTickArray = Split(ST_EndTickQ, "|")
        LastTickIndex = UBound(EndTickArray)
        ReDim Preserve EndTickArray(LastTickIndex)
        ReDim Preserve TimerArray(LastTimerIndex)
        QFirstTick = Val(ST_EndTickQ)
        QLastTick = Val(EndTickArray(LastTickIndex))

        If EndTick < QFirstTick Then
        'Front of queue
            ST_EndTickQ = EndTick & "|" & ST_EndTickQ
            ST_TimerQ = TimerDat & "|" & ST_TimerQ
            ST_TimerName = Val(ST_TimerQ)
        ElseIf EndTick > QLastTick Then
        'Back of queue
            ST_TimerName = TimerNameF(ST_TimerQ)
            ST_EndTickQ = ST_EndTickQ & "|" & EndTick
            ST_TimerQ = ST_TimerQ & "|" & TimerDat
        Else
        'Somewhere mid queue
            For i = 1 To LastTimerIndex
                If EndTick < EndTickArray(i) Then
                    ST_EndTickQ = Replace(ST_EndTickQ, EndTickArray(i - 1), _
                    EndTickArray(i - 1) & "|" & EndTick)
                    ST_TimerQ = Replace(ST_TimerQ, TimerArray(i - 1), _
                    TimerArray(i - 1) & "|" & TimerDat)
                    Exit For
                End If
            Next i
            ST_TimerName = TimerNameF(ST_TimerQ)
        End If
    End If
End Sub 'AddToQ

Sub RemoveFromQ(ByVal TimerName As String)
    Dim EndTickArray() As String
    Dim TimerArray() As String
    Dim LastTickIndex As Integer
    Dim LastTimerIndex As Integer
    Dim PosDel As Integer

    PosDel = InStr(ST_EndTickQ, "|")

    If PosDel = 0 Then
    'Last element remaining in queue
        ST_EndTickQ = vbNullString
        ST_TimerQ = vbNullString
        ST_TimerName = vbNullString
    Else
    '2+ elements in queue
        TimerArray = Split(ST_TimerQ, "|")
        LastTimerIndex = UBound(TimerArray)
        EndTickArray = Split(ST_EndTickQ, "|")
        LastTickIndex = UBound(EndTickArray)
        ReDim Preserve EndTickArray(LastTickIndex)
        ReDim Preserve TimerArray(LastTimerIndex)
        ST_TimerQ = vbNullString
        ST_EndTickQ = vbNullString
        For i = 0 To LastTimerIndex
            If InStr(TimerArray(i), TimerName) = 0 Then
                If ST_TimerQ = vbNullString Then
                    ST_TimerQ = TimerArray(i)
                    ST_EndTickQ = EndTickArray(i)
                    X = Len(ST_TimerQ) - InStr(ST_TimerQ, ":")
                    ST_TimerName = Right(ST_TimerQ, X)
                Else
                    ST_TimerQ = ST_TimerQ & "|" & TimerArray(i)
                    ST_EndTickQ = ST_EndTickQ & "|" & EndTickArray(i)
                End If
            End If
        Next i
    End If
End Sub 'RemoveFromQ

Sub GetNextQ()
    Dim PosDel As Integer

    PosDel = InStr(ST_EndTickQ, "|")
    If PosDel = 0 Then
    'Last element remaining in queue
        ST_EndTickQ = vbNullString
        ST_TimerQ = vbNullString
        ST_TimerName = vbNullString
    Else
    '2+ elements in queue
        ST_EndTickQ = Right(ST_EndTickQ, Len(ST_EndTickQ) - PosDel)
        ST_TimerQ = Right(ST_TimerQ, Len(ST_TimerQ) - InStr(ST_TimerQ, "|"))
        ST_TimerName = TimerNameF(ST_TimerQ)
    End If
End Sub 'GetNextQ

Public Function TimerNameF(ByVal TimerQ As String) As String
    Dim StrLen As Integer
    If InStr(ST_TimerQ, "|") Then
        StrLen = InStr(ST_TimerQ, "|") - InStr(ST_TimerQ, ":") - 1
    Else
        StrLen = Len(ST_TimerQ) - InStr(ST_TimerQ, ":")
    End If
    TimerNameF = Mid(ST_TimerQ, InStr(ST_TimerQ, ":") + 1, StrLen)
End Function

Sub TestSetTimer1()
'Call SubA every 5 seconds
    Call SetTimer("SubA", 600)
End Sub

Sub TestSetTimer2()
'Call SubB every second
    Call SetTimer("SubB", 200)
End Sub

Sub TestSetTimer3()
'Stop calling SubA
    Call SetTimer("SubA", 0)
End Sub

Sub TestSetTimer4()
'Stop calling SubB
    Call SetTimer("SubB", 0)
End Sub

Sub TestSetTimer5()
'Call SubC one time after a 3 second delay.
    Call SetTimer("SubC", -3000)
End Sub


Sub SubA()
    Debug.Print "SubA Queue: " & ST_TimerQ & ", EndTickQ: " & ST_EndTickQ
End Sub

Sub SubB()
    Debug.Print "SubB Queue: " & ST_TimerQ & ", EndTickQ: " & ST_EndTickQ
End Sub

Sub SubC()
    Debug.Print "SubC Queue: " & ST_TimerQ & ", EndTickQ: " & ST_EndTickQ
End Sub
Public ST_TimerName As String'作为新线程运行的子例程名称。
'两个字符串用于存储队列。
'第一个仅以tickcounts存储每个线程的开始时间。
'这使我可以更轻松地对队列进行排序。
'第二个字符串(ST_TimerQ)包含TimerDelay:TimerName,并在
'与排序的启动时间相同,因此它们保持同步。
Public ST_EndTickQ As String'队列字符串:以TickCounts为单位的触发时间。
公共ST_TimerQ As String'队列字符串:TimerDelay:TimerName
'允许您获取当前滴答数的新类。
公共ST_Timer作为Tick_Timer访问Tick Count的计时器
子设置计时器(ByVal TimerName为字符串,ByVal TimerDelay为长)
'启动一个名为TimerName的新线程,该线程在TimerDelay(毫秒)之后执行
'TimerName:要激活的子例程的名称。
“TimerDelay:
'-abs(-value)延迟后单次执行的值,
“+值以一段时间重复TimerName显示。
“0停止重复TimerName。
暗尾鳍一样长
Dim TimerDat As字符串
设置ST_定时器=新的滴答定时器
EndTick=ST_Timer.GetNumTicks+Abs(TimerDelay)
如果TimerDelay=0,则
”他叫道
RemoveFromQ TimerName
其他的
'插入队列,单个或重复由存储在TimerDat中的+/-延迟决定。
TimerDat=TimerDelay&“:”&TimerName
调用AddToQ(TimerDat,EndTick)
如果结束
“结束子”设置计时器
Sub-SetTimerQLoop()
'所有线程都连续合并到一个具有顺序
'插入排序。
'仅包含DoEvents函数的简单循环(允许运行其他VBA代码)
'循环,直到队列中的下一个线程需要启动。
'外部循环在队列中运行,直到EOQ。
暗尾鳍一样长
作为布尔的弱EOQ
关于错误转到错误处理程序
EOQ=错误
'设置计时器队列循环
做而不做(EOQ)
'延迟循环,DoEvents允许其他vba脚本在延迟期间运行。
做
多芬特
循环直到ST_Timer.GetNumTicks>=Val(ST_EndTickQ)
应用程序。运行ST_TimerName
如果Val(ST_TimerQ)>0,则
'使用pos延迟值重新插入队列线程。
EndTick=Val(ST_EndTickQ)+Val(ST_TimerQ)
TimerDat=Val(ST_TimerQ)和“&ST_TimerName”
调用AddToQ(TimerDat,EndTick)
如果结束
如果ST_TimerQ=vbNullString,则
EOQ=真
其他的
GetNextQ
如果结束
环
出口接头
错误处理程序:
“断键
“结束子”SetTimerQLoop
Sub AddToQ(ByVal TimerDat为字符串,ByVal EndTick为长)
Dim EndTickArray()作为字符串
Dim TimerArray()作为字符串
将索引设置为整数
将LastTimerIndex设置为整数
作为整数的Dim PosDatDel
昏暗的时光如长
作为字符串的Dim TimerName
暗QFirstTick与长QFirstTick一样
变暗,变长
PosDatDel=Len(TimerDat)-InStr(TimerDat,“:”)
TimerDelay=Val(TimerDat)
TimerName=右(TimerDat,PosDatDel)
如果ST_EndTickQ=vbNullString,则
“第一个计时器
ST_TimerName=TimerName
ST_EndTickQ=EndTick
ST_TimerQ=TimerDat
SetTimerQLoop
ElseIf InStr(ST_EndTickQ,“|”)则=0
“第二个计时器
如果EndTick