У меня есть книга Excel, которая запускает три запроса в базу данных для заполнения трех таблиц на скрытых листах и затем запускает три сценария обновления, чтобы вытащить эти данные до трех видимых презентационных листов (по одному на запрос). Запуск этого синхронно происходит довольно медленно: общее время обновления - это сумма времени каждого из трех запросов плюс сумма времени для каждого "обновления" script для запуска.
Я знаю, что VBA не многопоточен, но я подумал, что можно немного ускорить процесс, уволив запросы асинхронно (таким образом, позволяя выполнять некоторую работу по очистке, пока они выполнялись), а затем выполняйте работу по популяции/обновлению для каждого листа по мере возврата данных.
Я переписал свой script следующим образом (обратите внимание, что мне пришлось удалить строки подключения, строки запроса и т.д. и сделать общие переменные):
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
Типичный ожидаемый вывод отладчика:
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
Три запроса могут возвращаться в разных порядках, в зависимости от того, что заканчивается сначала, конечно, поэтому иногда типичный вывод упорядочивается по-разному - это ожидается.
Иногда, однако, один или два из обратных вызовов 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
и проверить, все ли они выполнены до запуска синхронных обновлений), поэтому вероятность из них перекрываются около нуля, но хотят знать, есть ли лучшее решение в первую очередь.