Функции таймера и генерация событий с обратным вызовом - PullRequest
3 голосов
/ 09 июня 2010

Мне нужен лучший способ вызывать события, чем SetTimer API в user32.dll.

Вы успешно использовали другие API таймера?Вы знаете другой способ вызывать события или вызывать функцию асинхронно в Excel?

Что я имею в виду под «лучше»?Мне нужно что-то, что может вытолкнуть сообщения из внутренней очереди, асинхронно.Проблема в том, что элементы помещаются в очередь внешним источником данных (рыночные данные Reuters), который вызывает события «Обновление данных» с сообщениями в диапазоне WM_Application;SetTimer работает, отправляя сообщения WM_Timer, семикратное слабое место в Windows Messaging, и мой процесс «Pop» никогда не получает шансов на запуск, даже при относительно небольшом трафике.

Если у вас есть предложения по улучшению архитектуры,не стесняйтесь предлагать их: но событие обновления входящих данных ОБЯЗАТЕЛЬНО ДОЛЖНО перейти в «приемник» события, как можно быстрее - Excel будет аварийно завершать работу, если возникает слишком много событий (или обратных вызовов), в то время как какой-то длинный, медленный процесс обрабатывает последнеесобытие, и тот, что до этого.Моя очередь имеет конечный размер, потому что в приложении всего несколько сотен акций: входящее обновление данных может либо просмотреть и обновить существующий элемент, если это обновленная цена для акции, которая уже находится в очереди, или выдвинуть новую акциютикер и цена в очередь.Таким образом, данные о ценах никогда не отбрасываются, потому что мы заняты, а четкое кодирование для функций push и peek означает, что приемник событий достаточно быстр, чтобы обеспечить стабильность Excel.

Но я остался в поискелучший способ вытаскивать элементы из очереди.

К сожалению, все другие API таймеров, которые я пробовал, не работают в VBA: они приводят к аварийному завершению приложения или заморозке выполнения кода.Вот список (гиперссылки ведут к документации MSDN):

  1. CreateTimerQueueTimer Lib "Kernel32.dll"
  2. CreateWaitableTimer Lib "kernel32"
  3. TimeSetEvent Lib "winmm.dll"

Я подозреваю, что проблемы с этими функциями не имеют ничего общего с плохим синтаксисом с моей стороны: я не знаюМодель потоков Windows достаточно хороша, чтобы сказать, что на самом деле происходит, когда они перезванивают, но в документации по одному из них говорится, что «Эта функция обратного вызова не должна вызывать функцию TerminateThread», и я понимаю, что VBA не может «утопить» возникшее событиес этим конкретным API таймера.

Считайте, что я знаю, что я знаю правильные вызовы API для Kill или удаления этих таймеров и связанных с ними очередей: пространство ограничено, а таймер Waitable использует отдельные Create-, Set-, Cancel- и вызовы API CloseHandle.И все функции TimerProc имеют немного разные подписи.

Вот стандартный вызов функции SetTimer:

lngTimerID = SetTimer (hWndXL, VBA.ObjPtr (Me), lngInterval, AddressOf TimerProc)

Я не буду утомлять васобъявление функции TimerProc и отступление от делегирования: если вы можете ответить на этот вопрос, вы уже видели все соответствующие примеры кода.

Я с нетерпением жду ваших ответов: у меня есть работающее приложение и осторожное кодированиеи сегрегация означает, что внутренняя задержка данных является приемлемой.Но я хотел бы думать, что я мог бы добиться большего успеха, даже если бы я ограничился выпуском отдельной книги Excel.

1 Ответ

2 голосов
/ 10 июня 2010

Я не на 100% уверен в том, что вы пытаетесь сделать, но API-интерфейсы таймера позволят вам асинхронно «планировать» вызов функции, но обратный вызов, конечно, не будет выполняться асинхронно, поскольку все это происходит в одном потоке.

Тем не менее, любой из API таймера обратного вызова должен работать в VBA, например (с использованием n-instances-> single callback):

Модуль таймера

Public Const TIME_PERIODIC As Long = 1
Public Const TIME_CALLBACK_FUNCTION As Long = &H0
Public Declare Function CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, src As Any, ByVal length As Long) As Long
Public Declare Function timeKillEvent Lib "winmm.dll" (ByVal uID As Long) As Long
Public Declare Function timeSetEvent Lib "winmm.dll" (ByVal uDelay As Long, ByVal uResolution As Long, ByVal lpFunction As Long, ByVal dwUser As Long, ByVal uFlags As Long) As Long

Public Sub tmrCallback(ByVal uID As Long, ByVal uMsg As Long, ByVal dwUser As Long, ByVal dw1 As Long, ByVal dw2 As Long)
    If (dwUser = 0) Then
        timeKillEvent uID
        Exit Sub
    End If

    Dim IDisp As Object
    Dim Obj As Class1

    CopyMemory IDisp, dwUser, 4&
    Set Obj = IDisp
    CopyMemory IDisp, 0&, 4&
    Obj.TimerMethod
End Sub

Class1

Private MYHTIMER As Long
Public Name As String

Private Sub stopTmr()
    If (MYHTIMER) Then timeKillEvent MYHTIMER
End Sub

Private Sub Class_Initialize()
    MYHTIMER = timeSetEvent(1000, 0, AddressOf Module1.tmrCallback, ObjPtr(Me), TIME_PERIODIC Or TIME_CALLBACK_FUNCTION)
End Sub

Private Sub Class_Terminate()
    stopTmr
End Sub

Public Sub TimerMethod()
    Static lCntr    As Long: lCntr = lCntr + 1
    Debug.Print "In " & Me.Name & ".TimerMethod()", "#"; lCntr, CLng(Timer - StartTime) & "secs"
    If (lCntr = 2) Then TestBlockThreadFor5Secs
    If (lCntr >= 3) Then stopTmr
End Sub

Тест

Public A As Class1
Public B As Class1

Sub test()
    Set A = New Class1: A.Name = "inst1"
    Set B = New Class1: B.Name = "inst2"
End Sub

Sub TestBlockThreadFor5Secs()
    Debug.Print "**blocking"
    t = Timer:
    Do Until Timer - t = 5: Loop
End Sub

Результат

In inst1.TimerMethod()      # 1           1secs
In inst2.TimerMethod()      # 1           1secs
In inst1.TimerMethod()      # 2           2secs
**blocking
In inst1.TimerMethod()      # 3           7secs
In inst2.TimerMethod()      # 2           7secs
**blocking
In inst2.TimerMethod()      # 3           12secs
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...