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.

The latest refresh result accepted while an older result remains separate.
The latest refresh result accepted while an older result remains separate.

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.

sample.zip
Download the complete worked example and build instructions (ZIP).

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 .