使用ADODB运行多个异步查询 - 回调并不总是触发

Kai*_*Kai 11 excel vba adodb excel-vba excel-2010

我有一个Excel工作簿,它向数据库发出三个查询以填充隐藏工作表上的三个表,然后运行三个"刷新"脚本将这些数据拉到三个可见的演示文稿表(每个查询一个).同步运行它非常慢:刷新的总时间是三个查询中每个查询的时间总和加上每个"刷新"脚本运行的时间总和.

我知道VBA不是多线程的,但我认为可以通过异步触发查询来加快速度(从而允许在执行时完成一些清理工作),并且然后在数据返回时为每个工作表执行填充/刷新工作.

我重写了我的脚本如下(请注意,我必须删除连接字符串,查询字符串等,并使变量通用):

Private WithEvents cnA As ADODB.Connection
Private WithEvents cnB As ADODB.Connection
Private WithEvents cnC As ADODB.Connection

Private Sub StartingPoint()
    'For brevity, only listing set-up of cnA here. You can assume identical
    'set-up for cnB and cnC
    Set cnA = New ADODB.Connection

    Dim connectionString As String: connectionString = "<my conn string>"
    cnA.connectionString = connectionString

    Debug.Print "Firing cnA query: " & Now
    cnA.Open
    cnA.Execute "<select query>", adAsyncExecute  'takes roughly 5 seconds to execute

    Debug.Print "Firing cnB query: " & Now
    cnB.Open
    cnB.Execute "<select query>", adAsyncExecute  'takes roughly 10 seconds to execute

    Debug.Print "Firing cnC query: " & Now
    cnC.Open
    cnC.Execute "<select query>", adAsyncExecute  'takes roughly 20 seconds to execute

    Debug.Print "Clearing workbook tables: " & Now
    ClearAllTables
    TablesCleared = True
    Debug.Print "Tables cleared: " & Now
End Sub

Private Sub cnA_ExecuteComplete(ByVal RecordsAffected As Long, ...)
    Debug.Print "cnA records received: " & Now
    'Code to handle the recordset, refresh the relevant presentation sheet here, 
    'takes roughly < 1 seconds to complete
    Debug.Print "Sheet1 tables received: " & Now
End Sub

Private Sub cnB_ExecuteComplete(ByVal RecordsAffected As Long, ...)
    Debug.Print "cnB records received: " & Now
    'Code to handle the recordset, refresh the relevant presentation sheet here, 
    'takes roughly 2-3 seconds to complete
    Debug.Print "Sheet2 tables received: " & Now
End Sub

Private Sub cnC_ExecuteComplete(ByVal RecordsAffected As Long, ...)
    Debug.Print "cnC records received: " & Now
    'Code to handle the recordset, refresh the relevant presentation sheet here, 
    'takes roughly 5-7 seconds to complete
    Debug.Print "Sheet3 tables received: " & Now
End Sub
Run Code Online (Sandbox Code Playgroud)

典型的预期调试器输出:

Firing cnA query: 21/02/2014 10:34:22
Firing cnB query: 21/02/2014 10:34:22
Firing cnC query: 21/02/2014 10:34:22
Clearing tables: 21/02/2014 10:34:22
Tables cleared: 21/02/2014 10:34:22
cnB records received: 21/02/2014 10:34:26
Sheet2 tables refreshed: 21/02/2014 10:34:27
cnA records received: 21/02/2014 10:34:28
Sheet1 tables refreshed: 21/02/2014 10:34:28
cnC records received: 21/02/2014 10:34:34
Sheet3 tables refreshed: 21/02/2014 10:34:40
Run Code Online (Sandbox Code Playgroud)

当然,这三个查询可以以不同的顺序返回,具体取决于哪个完成,因此有时典型的输出以不同的顺序排序 - 这是预期的.

然而,有时候,一个或两个cnX_ExecuteComplete回调根本不会发射.经过一段时间的调试,我很确定这样做的原因是,如果一个记录集在其中一个回调当前正在执行时返回,则不会发生调用.例如:

  • 查询A,B和C都在0时触发
  • 查询A在时间3首先完成,cnA_ExecuteComplete触发
  • 查询B在时间5完成第二个
  • cnA_ExecuteComplete仍在运行,所以cnB_ExecuteComplete永远不要开火
  • cnA_ExecuteComplete 在8时完成
  • 查询C在时间10完成,cnC_ExecuteComplete触发
  • 查询C在时间15完成

我的理论是否正确,这是问题所在?如果是这样,是否可以解决这个问题,或者调用"等待"直到当前代码执行而不是仅仅消失?

一种解决方案是在cnX_ExecuteComplete回调期间做一些非常快速的事情(例如,Set sheet1RS = pRecordset在运行刷新脚本同步之前检查它们是否全部完成),因此它们重叠的几率大约为零,但是想要先知道是否有更好的解决方案.

小智 9

我想我无法解释为什么你的'刷新脚本'并不总是激发.这是一种奇怪的行为,有时它们会运行,有时却不会运行.我无法真正看到您的整个脚本,但我可以向您展示我如何采用您的代码并使其每次都能正常工作.

注意:您的问题与使用adAsyncExecute参数未触发的ExecuteComplete ADODB Connection事件有某种关联

我在SQL服务器上添加了3个存储过程; sp_WaitFor5,sp_WaitFor10,sp_WaitFor20模拟的查询执行时间的延迟.

很简单

CREATE PROCEDURE sp_WaitFor5
AS
WAITFOR DELAY '00:00:05'
Run Code Online (Sandbox Code Playgroud)

所有3个延迟.

然后在我的Module1我添加了一个非常简单的代码来调用自定义类

Option Explicit

Private clsTest As TestEvents

Sub Main()
    Cells.ClearContents
    Set clsTest = New TestEvents
    Call clsTest.StartingPoint
End Sub
Run Code Online (Sandbox Code Playgroud)

然后我将类模块重命名为TestEvents并添加了稍微修改过的代码版本

Option Explicit

Private WithEvents cnA As ADODB.Connection
Private WithEvents cnB As ADODB.Connection
Private WithEvents cnC As ADODB.Connection

Private i as Long

Public Sub StartingPoint()

    Dim connectionString As String: connectionString = "Driver={SQL Server};Server=MYSERVER\INST; UID=username; PWD=password!"

    Debug.Print "Firing cnA query(10 sec): " & Now
    Set cnA = New ADODB.Connection
    cnA.connectionString = connectionString
    cnA.Open
    cnA.Execute "sp_WaitFor10", adExecuteNoRecords, adAsyncExecute

    Debug.Print "Firing cnB query(5 sec): " & Now
    Set cnB = New ADODB.Connection
    cnB.connectionString = connectionString
    cnB.Open
    cnB.Execute "sp_WaitFor5", adExecuteNoRecords, adAsyncExecute

    Debug.Print "Firing cnC query(20 sec): " & Now
    Set cnC = New ADODB.Connection
    cnC.connectionString = connectionString
    cnC.Open
    cnC.Execute "sp_WaitFor20", adExecuteNoRecords, adAsyncExecute

End Sub


Private Sub cnA_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection)
    Debug.Print vbTab & "cnA_executeComplete START", Now
    For i = 1 To 55
        Range("A" & i) = Rnd(1)
    Next i
    Debug.Print vbTab & "cnA_executeComplete ENDED", Now
End Sub

Private Sub cnB_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection)
    Debug.Print vbTab & "cnB_executeComplete START", Now
    For i = 1 To 1000000
        Range("B" & i) = Rnd(1)
    Next i
    Debug.Print vbTab & "cnB_executeComplete ENDED", Now
End Sub

Private Sub cnC_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection)
    Debug.Print vbTab & "cnC_executeComplete START", Now
    For i = 1 To 55
        Range("C" & i) = Rnd(1)
    Next i
    Debug.Print vbTab & "cnC_executeComplete ENDED", Now
End Sub
Run Code Online (Sandbox Code Playgroud)

我真的没有太大的改变,除了额外的参数进行Execute一些代码填充activesheet只是花时间.


现在,我可以运行不同的变体/配置.我可以旋转连接对象的执行时间.我可以有cnA5秒,cnB10秒,cnC20秒.我可以交换/调整每个_ExecuteComplete事件的执行时间.

从我自己的测试中我可以向你保证,所有3个都会被执行.

这里有一些基于类似于你的配置的日志

Firing cnA query(10 sec): 24/02/2014 12:59:46
Firing cnB query(5 sec): 24/02/2014 12:59:46
Firing cnC query(20 sec): 24/02/2014 12:59:46
    cnB_executeComplete START             24/02/2014 12:59:51 
    cnB_executeComplete ENDED             24/02/2014 13:00:21 
    cnA_executeComplete START             24/02/2014 13:00:21 
    cnA_executeComplete ENDED             24/02/2014 13:00:21 
    cnC_executeComplete START             24/02/2014 13:00:22 
    cnC_executeComplete ENDED             24/02/2014 13:00:22
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,您可以看到,所有3个查询都是异步触发的.

cnA5秒后返回句柄,这使得cnB第一个事件("刷新脚本")在层次结构中运行cnC时间最长.

由于首先cnB 回来,它会触发它的cnB_ExecuteComplete事件程序.它cnB_ExecuteComplete本身设置需要花费一些时间执行(迭代100万次并用随机数填充B列.注意:cnA填充A列,cnB col B,cnC col C).查看上面的日志需要30秒才能运行.

cnB_ExecuteComplete正在执行其工作/占用资源(并且如您所知VBA是单线程的)时,cnA_ExecuteComplete事件将被添加到TODO进程的队列中.所以,你可以把它想象成一个队列.当事情正在被照顾下一件事情必须等到最后.


如果我改变配置; cnA5秒,cnB10秒,cnC20秒,然后让每个'刷新脚本'迭代100万次

Firing cnA query(5 sec): 24/02/2014 13:17:10
Firing cnB query(10 sec): 24/02/2014 13:17:10
Firing cnC query(20 sec): 24/02/2014 13:17:10
one million iterations each
    cnA_executeComplete START             24/02/2014 13:17:15 
    cnA_executeComplete ENDED             24/02/2014 13:17:45 
    cnB_executeComplete START             24/02/2014 13:17:45 
    cnB_executeComplete ENDED             24/02/2014 13:18:14 
    cnC_executeComplete START             24/02/2014 13:18:14 
    cnC_executeComplete ENDED             24/02/2014 13:18:44 
Run Code Online (Sandbox Code Playgroud)

从第一个例子中清楚地证明了我的观点.

此外,尝试cnA5秒,cnB5秒,cnC5秒

Firing cnA query(5 sec): 24/02/2014 13:20:56
Firing cnB query(5 sec): 24/02/2014 13:20:56
Firing cnC query(5 sec): 24/02/2014 13:20:56
one million iterations each
    cnB_executeComplete START             24/02/2014 13:21:01 
    cnB_executeComplete ENDED             24/02/2014 13:21:31 
    cnA_executeComplete START             24/02/2014 13:21:31 
    cnA_executeComplete ENDED             24/02/2014 13:22:01 
    cnC_executeComplete START             24/02/2014 13:22:01 
    cnC_executeComplete ENDED             24/02/2014 13:22:31
Run Code Online (Sandbox Code Playgroud)

这也完成/执行所有3.


就像我说的那样,我看不到你的整个代码,也许你的代码中某处有一个未处理的错误,也许有些东西误导你认为一个人_ExecuteComplete根本没有执行.尝试更改您的代码以反映我给您的代码并自行运行一些文本.我期待着您的反馈.