How to Apply Only the Latest Grid Refresh in Pascal
A slow request can finish after the user has already chosen something else. Without a clear admission rule, yesterday's selection can suddenly replace today's visible page. Disabling the whole form avoids some interaction, but it does not solve the relationship between accepted rows, pending requests and a worker that is still unwinding.
Sphere 10 Pascal Framework's grid distinguishes refresh admission from background execution. RefreshSuspended holds new provider work, CancelActiveFetch requests cancellation of active asynchronous work, and the grid retains the latest pending request for later admission. The important result is a coherent view, not forcibly stopping a thread at an arbitrary instruction.
Contents
Separate the Three Decisions
First decide whether new work may start. Suspension is useful while several UI settings form one logical change. Next decide whether old work is still relevant. Cancellation can make its result obsolete while allowing its provider to return normally. Finally decide when the latest request should be admitted, including any quiet-period policy selected on the grid's refresh throttle.
These decisions are deliberately separate. CancelActiveFetch does not change the throttle mode or promise that the worker has exited before the call returns. It does not destroy the provider. Existing accepted rows remain available until a later page replaces them.
Batch a Coherent View Change
The executable example uses synchronous fetches to test refresh admission and the final selected page without relying on worker timing. It configures ten-row manual paging before attaching the data source, so the initial fetch establishes a valid page count before the suspended page changes. It suspends the grid, invalidates the source, makes several page requests, checks that no fetch was admitted, and resumes the final request. The same public controls are available when FetchDataInThread is enabled.
This is a focused admission example, not a simulated network benchmark or a claim that it has reproduced every possible worker race. The repository's separate refresh fixtures exercise actual worker retirement and obsolete-result cases. Keeping those proof boundaries explicit makes the tutorial easier to adapt correctly.
program ArticleSample;
{$IFDEF FPC}{$mode Delphi}{$H+}{$ENDIF}
uses
{$IFDEF FPC}Interfaces, Forms, Controls,{$ELSE}Vcl.Forms, Vcl.Controls,{$ENDIF}
Classes, SysUtils, Variants, Generics.Collections, UCommon.Data, UVisualGrid;
type
TNumberSource = class(TCustomDataSource<Integer>)
public
FetchCount: Integer;
protected
function GetColumns: TDataColumns; override;
procedure FetchAll(const AContainer: TList<Integer>); override;
function GetItemField({$IFDEF FPC}constref{$ELSE}const{$ENDIF} AItem: Integer; const ABindingName: String): Variant; override;
end;
function TNumberSource.GetColumns: TDataColumns;
begin
Result := TDataColumns.Create(TDataColumn.From('Value', True));
end;
procedure TNumberSource.FetchAll(const AContainer: TList<Integer>);
var I: Integer;
begin
Inc(FetchCount);
for I := 1 to 100 do AContainer.Add(I);
end;
function TNumberSource.GetItemField({$IFDEF FPC}constref{$ELSE}const{$ENDIF} AItem: Integer; const ABindingName: String): Variant;
begin
if ABindingName <> 'Value' then raise EDataTable.Create('Unknown binding');
Result := AItem;
end;
var
Window: TForm;
Source: TNumberSource;
Grid: TVisualGrid;
Column: TVisualColumn;
Before: Integer;
begin
Application.Initialize;
Window := TForm.CreateNew(nil);
try
Source := TNumberSource.Create(Window);
Source.SnapshotCacheEnabled := True;
Grid := TVisualGrid.Create(Window);
Grid.Parent := Window;
Grid.SetBounds(0, 0, 640, 360);
Grid.FetchDataInThread := False;
Column := Grid.AddColumn('Number');
Column.Binding := 'Value';
Column.Filters := SORTABLE_NUMERIC_FILTER;
Grid.ManualPageSize := 10;
Grid.PageMode := vpmManual;
Grid.DataSource := Source;
Before := Source.FetchCount;
Grid.RefreshSuspended := True;
try
Grid.CancelActiveFetch;
Source.InvalidateSnapshot;
Grid.PageIndex := 1;
Grid.RefreshGrid;
Grid.PageIndex := 2;
Grid.RefreshGrid;
if Source.FetchCount <> Before then raise Exception.Create('Suspension admitted a fetch');
finally
Grid.RefreshThrottle.Cancel;
Grid.RefreshSuspended := False;
end;
if Grid.RowCount <> 10 then raise Exception.Create('Latest page did not contain 10 rows');
if (Grid.PageIndex <> 2) or (Integer(Grid.Rows[0].Value) <> 21) then
raise Exception.Create('Latest requested page was not published');
WriteLn('No fetch during suspension; resumed page starts at 21 and contains 10 rows');
finally
Window.Free;
end;
end.
Check What Was Published
No fetch during suspension; resumed page starts at 21 and contains 10 rows
The provider count cannot increase inside the suspended region. On resumption, the retained request supplies page two, whose ten-row result begins with the value twenty-one. The example also cancels the refresh throttle while suspended, explicitly choosing immediate admission on resume. An application that wants to preserve a pending quiet interval should retain that policy instead.
For an interactive search, call refresh APIs from the UI thread. Capture provider inputs independently of controls before background work reads them. A worker should use its captured query or safe data-source state, with control access kept on the UI thread.
Let Old Work Retire Safely
Cooperative cancellation is not thread termination. An active provider must reach its normal cancellation/completion path. Keep the source, callback targets and any borrowed data alive until that worker has retired. Destroying the source immediately after requesting cancellation turns a presentation problem into a lifetime defect.
If you replace a provider, treat its retirement as part of the owning screen's shutdown or replacement workflow. Avoid joining a worker while holding a lock that the worker needs to complete. The grid's public cancellation method is nonblocking, while the application owns the surrounding resource lifecycle.
IsUpdating describes an asynchronous page fetch. LoadingVisible describes caller-held loading presentation. Neither property alone is a complete cancellation or ownership protocol.
Keep Feedback Honest
A refresh can be waiting for admission, actively fetching, cancelled, failed or completed. Do not display a successful empty page merely because old work was cancelled. Preserve useful accepted rows and let the existing fetch/error presentation communicate the current state.
For slow operations, the grid supplies loading feedback and can retain a provider error until a successful retry. A separately held ShowLoading card belongs to its caller and is not automatically dismissed by unrelated fetch completion. Keep that responsibility with the operation that created it.
The grid source documents the individual methods and the unified demo provides an interactive delayed-fetch example. Combine this admission model with snapshot reuse for stable data and event coalescing for noisy input. The Framework supplies the reusable machinery, while the screen determines which request represents the user's current intent.
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 .lpk or Delphi runtime package. Pascal uses these source/package workflows. The C# Framework is distributed separately through NuGet.
The attached application sample was compiled and executed with Free Pascal 3.2.0 on Windows against
this public source revision
, with range and overflow checks. Its Build.ps1 accepts the Framework, compiler, Lazarus and output paths so the example does not need machine-specific paths. Lazarus UI examples require the LCL Win32 widgetset. A successful FPC run does not claim a new Delphi build. Follow the repository's separate Delphi/VCL project and resource instructions there.
Download the complete runnable sample.
Continue the Series
How to Build a Grid That Adapts to Its Window
How to Page and Sort Data Without Fetching the Same Snapshot Again
How to Turn a Burst of UI Events into One Useful Refresh
For the broader Sphere 10 development toolkit, see the Sphere10 Framework overview .