How to Apply Only the Latest Grid Refresh in Pascal
A grid that loads its pages on a background thread has a race to manage. If the user changes the page or the query while a fetch is running, the earlier fetch can finish afterwards and overwrite what the user asked for most recently.
TVisualGrid in Sphere 10 Pascal Framework owns that request lifecycle. It keeps only the latest pending request, runs one fetch at a time, and discards the result of a fetch that was cancelled. This article describes that pipeline and the three members an application uses to control it, which are RefreshSuspended, CancelActiveFetch and RefreshThrottle.
1. Apply several changes as one request
When several settings change together, each assignment would normally be a request of its own. Suspending refreshes turns the group into one change, because nothing reaches the data source until the suspension ends.
Grid.RefreshSuspended := True;
try
Grid.CancelActiveFetch;
Source.Revision := 1;
Grid.PageIndex := 2;
Grid.RefreshGrid;
Grid.PageIndex := 3;
Grid.RefreshGrid;
The sample starts this block while a worker is still inside the data source. The call to CancelActiveFetch is shown for clarity, since suspending already cancels the active fetch. The requests for page indexes 2 and 3 are recorded, and the second replaces the first. The data source is not called for either.
If the earlier worker finishes during the suspension, its result is discarded and the previously accepted rows remain visible.
2. Resume with the latest request
Clearing RefreshSuspended admits the retained request, which here is page index 3.
finally
Source.ReleaseFetch.SetEvent;
Grid.RefreshThrottle.Cancel;
Grid.RefreshSuspended := False;
end;
If the throttle is in a quiet-period mode and is still waiting, the resumed request respects the remaining interval. Calling RefreshThrottle.Cancel first, as the sample does, makes the resume immediate. An application that wants the quiet period, for example after typing in a search box, can leave that call out.
The sample confirms that exactly one fetch follows the resume.
Cancelled fetch finished; rows on screen unchanged
Resume published page index 3 from revision 1
Fetches after resume: 1
3. How the grid admits and accepts a refresh
The suspend and resume sequence relies on the way the grid handles every request. Every client action that needs data, such as assigning PageIndex, calling RefreshGrid or changing a filter, records a pending request. A newer request replaces an older pending one, so the grid never queues a backlog.
A pending request is admitted to the data source only when no fetch is active and refreshes are not suspended. With FetchDataInThread set to True, the admitted request runs IDataSource.FetchPage on a worker thread. When the worker completes, the grid decides on the UI thread whether to publish its rows.
Completion and acceptance are therefore separate events. A cancelled worker is allowed to finish, but its rows never reach the screen. The grid restores the page it was showing and then admits whatever request is pending.
4. The members that control the pipeline
| Member | Effect |
FetchDataInThread |
Runs each fetch on a worker thread. When False, the fetch runs synchronously inside the request. |
CancelActiveFetch |
Marks the active fetch as cancelled and asks its thread to terminate cooperatively. It does not wait for the thread, and the rows on screen stay as they are. |
RefreshSuspended |
While True, requests and layout continue to be recorded but nothing is admitted to the data source. Setting it also cancels the active fetch. Setting it back to False admits the one latest request. |
RefreshThrottle |
A TThrottledEvent owned by the grid that can delay admission until a burst of requests has gone quiet. Its default mode, temNone, admits immediately. |
IsUpdating |
True while a background fetch is running or waiting to be completed on the UI thread. |
All of these are UI-thread members. Changing PageMode cancels the active fetch in the same way.
5. What the data source must do
Cancellation is cooperative. The grid never kills or joins a worker from CancelActiveFetch or from the RefreshSuspended setter, so a data source that ignores the request simply runs to completion and has its result dropped. That places three obligations on the provider.
-
Stay alive. The data source and anything it captured must outlive the worker, including a cancelled one. Destroying the grid waits for an outstanding worker, so release anything the worker is blocked on first.
-
Read inputs once. Capture the values a fetch depends on at the start of
FetchAll, as the sample does with its revision number, so one fetch works from one consistent input. -
Stay off the UI.
FetchAllruns on the worker thread and must not read or change controls.
InterlockedIncrement(FetchCount);
CapturedRevision := Revision;
The visual grid unit documents these members beside their declarations. The UI event coalescing article looks at turning a burst of events into one refresh.
6. Get the Framework and run the example
Get Sphere 10 Pascal Framework and follow its package instructions . Use the units directly, or build the optional Lazarus or Delphi runtime package.
The sample was compiled and run on Windows with Free Pascal 3.2.0 and the Lazarus Win32 widgetset, against
source revision
2cc6558
. Its Build.ps1 accepts the Framework, compiler, Lazarus and output paths.
Download the complete runnable sample.
For the broader Sphere 10 development toolkit, see the Sphere10 Framework overview .
Author: Herman Schoenfeld