WklTimerList
WklTimerList runs scheduled tasks for an Access form: a search that starts 400 ms after the last
key press, a counter refreshed every 30 seconds, a queue of web requests that keeps to the limit
of a data service. One control holds any number of tasks, each with its own key, delay, interval
and rules for overlaps, retries and time limits. It is visible in Design view and invisible in
Form view.
Since release 1.60.0. The kinds wttEvent and wttHttp are available; wttSteps, wttFile, wttChecksum, wttZip and wttProgram are part of the contract but refused with wkeNotAvailable in this version.
Get started
- Search a list while the user types: a delayed search and a counter refreshed every 30 seconds.
Common tasks
- Start a search 400 ms after the last key press: one task with
Delay = 400,Restartin theChangeevent of the text box. - Refresh a counter every 30 seconds: one task with
Interval = 30000,AutoStart = True. - Send 1 700 requests to a service that allows 300 per minute: one task of kind
wttHttpwithRateLimit = 300,RateWindow = 60000, oneRequests.Addper row with the key of the row asTag(How to: Send many requests to a web service at its pace).
Visual aid
graph TD
A[Form opens] --> B{AutoStart}
B -->|True| C[Tasks waiting]
B -->|False| D[Tasks stopped until Start]
D --> C
C -->|due, wttEvent| E[TaskDue posted to the form]
E -->|the procedure returned| C
C -->|due, wttHttp| F[Queue sends its requests]
F -->|each answer| R[RequestCompleted or RequestFailed]
R --> F
F -->|queue empty| G[TaskCompleted]
G -->|Interval greater than 0| C
X[Form closes] --> J[All tasks end, no event afterwards]Key features
- Several tasks per form, each identified by a key; started, stopped, paused and resumed one by one or all together.
- First due time after a delay from the start, or at a clock time.
- Repetition in a fixed interval that counts from the due time, not from the end of the run: a 2-second run does not shift a 30-second series.
- Overlap rule for due times that pass while a run is still going: drop them (
wtoSkip) or keep exactly one (wtoOnce). The queue never grows. - Retries and time limits per task.
- Kinds of work: a VBA event (
wttEvent), VBA in steps (wttSteps), and jobs the control runs itself in the background: web request (wttHttp), copy or move files (wttFile), checksum (wttChecksum), ZIP (wttZip), external program (wttProgram). - Paced web requests: a minimum gap, a limit per time window, a pause when the service answers 429, a halt of the whole queue on 401 until the application signs in again.
Core concepts
A timer does not make VBA parallel. VBA, forms, DAO and the Access object model run on the
main thread of Access. A wttEvent or wttSteps task runs on that thread when it is due; only
the background kinds run elsewhere, and their results come back to the main thread as events.
Events are posted, never raised from inside another event. VBA does not deliver an event of
an object while an event procedure of the same object is running. The control therefore posts
every due event to its own message window and delivers it after the running procedure has ended. A MsgBox in an event procedure delays the
next event; it does not lose it.
A message window of its own. An ActiveX control that is invisible or disabled in Access has no
window and receives no messages. The timer list uses a hidden message-only window that lives from the opening to the closing of the form, whatever Visible and Enabled say.
The life of a task ends with the form. When the form closes, every task stops, running background jobs are canceled and no event arrives afterwards.
The clock. Delays and intervals count on a monotonic counter, so a user who sets the clock of
Windows forward or back does not make a task fire early or late. StartAt is a local clock time.
Rules
These rules decide when a task is due. They are the same for every kind of task.
| Situation | Behavior |
|---|---|
Start with Delay = 0 and no StartAt | Due at once. |
StartAt in the future | Due at that clock time. |
StartAt in the past, Interval = 0 | Due at once. |
StartAt in the past, Interval > 0 | Due at the next time of the series StartAt + n × Interval after now. |
Run completed, Interval > 0 | Next due time = previous due time + Interval. |
Run completed, Interval = 0 | State wtsDone. |
Due times passed during the run, wtoSkip | They are dropped; next due time is the next one of the series after now. |
Due times passed during the run, wtoOnce | One run follows at once; the rest are dropped. |
Trigger during a run, wtoOnce | One further run is noted; more triggers do not add more. |
Trigger during a run, wtoSkip | Ignored. |
Run failed, Retries left | The same run (same RunId) repeats after RetryDelay; TaskFailed with WillRetry = True. |
| Run failed, no retries left | TaskFailed with WillRetry = False; LastError keeps the text; the series goes on if Interval > 0. |
| Run canceled | Never repeated. |
Run takes longer than Timeout | Background jobs: the attempt is canceled (for a web request queue: per request, see below). A wttEvent run is the VBA event procedure and cannot be interrupted; Timeout has no effect on it. |
Pause | The time left is kept; Resume goes on with it. |
Stop during a run | The run ends normally; nothing more is scheduled, no noted run follows. |
Restart | The delay begins again from now: the delayed search after each key press. |
Enabled = False | Never due, also not on Trigger. |
Rules for web requests (wttHttp). A task of this kind is a queue: it holds any number of
requests, sends them one after another and keeps the pace below for all of them together.
| Situation | Behavior |
|---|---|
MinGap | The least time between two request starts of the queue. |
RateLimit, RateWindow | At most RateLimit starts in any RateWindow milliseconds. Both can be set in any order; Start of a queue with a RateLimit but no RateWindow is refused with wkeInvalidValue. |
Answer 429 with Retry-After | The whole queue pauses for that time (seconds or an HTTP date, RFC 9110, section 10.2.3); a longer pause already set stays. QueuePaused reports reason and end. The request is sent again. |
Answer 429 without Retry-After | As above, for RateWindow milliseconds; without a RateWindow, for 60 seconds. |
| Answer 401 | The whole queue halts; only QueueHalted reports it, with the status and the response headers. The application signs in again on the main thread, sets the shared headers and calls Resume; the halted request is sent again first. |
| A second 401 in a row for the same request | RequestFailed for it (so a failed sign-in does not loop), and the queue halts again with QueueHalted. |
| No answer (connection broken, time limit) | Sent again after RetryDelay while Retries are left, before every other waiting request. |
Timeout, Retries, RetryDelay | Apply per request and per attempt, not to the whole queue. |
| Any other answer, also 5xx | Reported with RequestFailed, never sent again: a repetition could do the work twice. |
| A repetition | Sends the same own headers, the same body and the same Idempotency-Key. Shared headers are read at every send, so a new session header after a 401 applies to it and to every request still waiting. |
Stop | Nothing more is sent. The request on the way is not canceled: its answer comes and is reported, because a closed connection can leave a change at the service whose outcome nobody knows. The requests not yet sent stay with state wrsWaiting, without an event. Start sends them. |
Requests.Clear or Request.Cancel | Each request not yet done ends with RequestCancelled and state wrsCancelled; a request on the way has its connection closed and is never sent again. Its outcome at the service is unknown: send it again yourself with the same IdempotencyKey if the service supports it. |
| Several queues against the same service | Do not share their pace: each keeps its own MinGap and RateLimit. Put all requests to one service in one queue, for example on the shell form. |
| The queue is empty | TaskCompleted with Result = Array(completed, failed), the numbers of requests completed and failed for good in this run. New requests added later start the queue again while the task is started. |
Encoding, sign-in and secrets:
| Topic | Rule |
|---|---|
| Request body | Sent as UTF-8 with the ContentType of the request (default application/json; charset=utf-8). |
| Response text | Result.Text is decoded by the charset of the response; without one, as UTF-8. |
| Windows sign-in | UseWindowsLogin = True signs in with the Windows account of the Access process (Negotiate, NTLM) when the service asks for it. Default False. |
| Secrets | Shared headers, own headers, request and response bodies stay in memory only. The trace and error texts name the method, the address without its query and the status; never a header value or a body. |
Contract
WklTimerList (control) with the collection Tasks of WklTimerTask.
Member of WklTimerTask | Meaning |
|---|---|
Key | Unique text, compared without regard to case. |
Enabled | The task takes part. |
Kind | wttEvent, wttSteps, wttHttp, wttFile, wttChecksum, wttZip, wttProgram. |
Delay, StartAt | First due time: milliseconds after Start, or a clock time. |
Interval | Repetition in milliseconds; 0 = once. |
Overlap | wtoSkip or wtoOnce. |
Timeout, Retries, RetryDelay | Time limit of a run (0 = none), retries after a failure, wait before a retry. |
Tag | Any value; passed unchanged to every event. |
State | wtsStopped, wtsWaiting, wtsRunning, wtsPaused, wtsDone (read only). |
RunCount, LastRun, NextRun, LastError | Read only. |
Start, Stop, Pause, Resume, Trigger, Restart | See Rules. |
Member of WklTimerList | Meaning |
|---|---|
Tasks | The tasks; Add(Key), Remove(Key), Item(Key), Count. |
Start, Stop, Pause, Resume | For all tasks. |
AutoStart | Start all tasks when the form opens. |
Running | The number of runs going on. |
MaxParallel | Background jobs at the same time; default 1. Reserved for the file, checksum, ZIP and program kinds; each web request queue always sends one request at a time, and several queues run side by side. |
| Event | When |
|---|---|
TaskDue(Key, RunId, Tag, Cancel) | A wttEvent run is due; the event procedure is the run. Cancel = True skips it; the series goes on. A web request queue raises TaskStarted instead, with its first request. |
TaskStep(Key, RunId, Step, Done, Progress) | wttSteps only: once per step; Access gets control back between steps. |
TaskStarted(Key, RunId) | A background job started. |
TaskProgress(Key, RunId, Percent, Text) | A background job reports progress. |
TaskCompleted(Key, RunId, Result) | A background run ended successfully; for a web request queue, the queue became empty. A wttEvent run raises none: its run is TaskDue. |
TaskFailed(Key, RunId, Number, Description, WillRetry) | A run failed. |
TaskCancelled(Key, RunId, Reason) | A run was canceled (by Stop of a background job, by the time limit, by closing the form). |
Result by kind: files and ZIP Files, Bytes; checksum Hash; program ExitCode, Output,
ErrorOutput.
Web request queue
Members of a WklTimerTask of kind wttHttp:
| Member | Meaning |
|---|---|
Requests | The requests of the queue; Add(Method, Url, [Body], [Tag], [ContentType]) As WklHttpRequest, Item(Id), Count (done ones too), Waiting (not yet sent), Clear, Forget (removes done requests, returns how many). |
SharedHeader(Name) | Read and write a header sent with every request of the queue, for example X-Api-Key, Authorization, the session header of a second factor. An empty value removes it. |
MinGap, RateLimit, RateWindow | The pace, see Rules. |
UseWindowsLogin | Sign in with the Windows account, see above. |
Halted, PausedUntil | Read only: the queue waits for Resume after a 401; the end of a pause after 429, or Null. |
Members of WklHttpRequest:
| Member | Meaning |
|---|---|
Id | Number of the request, unique in the task; passed to every request event. |
Method, Url, Body, ContentType | As given to Add; changeable until the request is sent. |
Header(Name) | An own header of this request. |
IdempotencyKey | Sent as Idempotency-Key; a new GUID by default, the same for every repetition. An empty value sends none. |
Tag | Any value; passed unchanged to every request event, for example the key of a row. |
State | wrsWaiting, wrsSending, wrsCompleted, wrsFailed, wrsCancelled. |
Cancel | Ends the request: not yet sent, it is taken out of the queue; on the way, the connection is closed. |
Events of the queue (in addition to the task events above):
| Event | When |
|---|---|
RequestCompleted(Key, Id, Tag, Status, Result) | An answer 2xx or 3xx arrived. Result: Status, Text, Header(Name), Headers. More requests may be added here. |
RequestFailed(Key, Id, Tag, Status, Description, WillRetry, Result) | Another answer, or no answer; Status 0 when none came. Result holds the answer (Text, Header(Name)) when one came, for example a problem+json message; Nothing otherwise. |
RequestCancelled(Key, Id, Tag, Reason) | By Cancel, Requests.Clear, the time limit or the closing of the form. |
QueuePaused(Key, Reason, ResumeAt) | A 429 paused the queue until ResumeAt (local time). |
QueueHalted(Key, Id, Status, Headers) | A 401 halted the queue; Headers holds the response headers as text. |
QueueResumed(Key) | The pause ended or Resume was called. |
Administration
Troubleshooting
None yet.
For developers
Related topics
- WklApplication: the application as a whole, where a shell form with its timers lives.