Skip to content

Add asynchronous record reading - #284

Merged
chrisrichards merged 1 commit into
mainfrom
feature/async-read
Jul 6, 2026
Merged

Add asynchronous record reading#284
chrisrichards merged 1 commit into
mainfrom
feature/async-read

Conversation

@chrisrichards

Copy link
Copy Markdown
Member

Summary

Adds true async I/O to the read path — until now DbfDataReader inherited DbDataReader's default ReadAsync, which just wraps the synchronous Read() in a completed task.

Design: one buffered async read per record, synchronous parsing. DbfRecord.ReadAsync fills the existing reused record buffer with Stream.ReadAsync(Memory<byte>, CancellationToken) (same partial-read loop as the sync path), then parses via the existing span-based value readers. The parse is shared with the sync path through a private ParseRecord (spans can't live in async methods, which makes the split natural). This is deliberately the opposite of the 2018 fork's approach (per-field async through a hand-ported async BinaryReader) — one awaited read per record instead of one per field, and a single code path for all value parsing.

New API:

  • DbfRecord.ReadAsync(Stream, CancellationToken)ValueTask<bool>
  • DbfTable.ReadAsync(DbfRecord, CancellationToken)ValueTask<bool>
  • DbfTable.ReadRecordAsync(CancellationToken)ValueTask<DbfRecord>
  • DbfDataReader.ReadAsync(CancellationToken) — real override with the same skip-deleted loop as Read()

ValueTask internals mean no per-record task allocation when reads complete synchronously from the FileStream buffer (the common case); the Task<bool>-returning override benefits from the cached true/false task optimisation. Cancellation flows through to the underlying stream. EndOfStreamException handling matches the sync path.

Scope notes:

  • File opening is unchanged (no FileOptions.Asynchronous by default — with one buffered read per record the handle mode matters little, and it would slow the dominant sync path; callers wanting a fully async handle can pass their own FileStream via the stream constructors).
  • Memo values are still resolved synchronously during parsing (small seeked reads); can be made async separately.
  • Possible follow-ups: async table open (OpenAsync for header/columns), IAsyncEnumerable<DbfRecord>.

Test plan

New AsyncReadTests (7 tests): full-table async read compared row-by-row against the sync path (dbase_03, and dbase_30 to cover memo resolution inside async reads), RecordIndex tracking, Seek + ReadAsync, ReadRecordAsync, skip-deleted through the reader override (12 of 14 rows), and cancellation (pre-cancelled token throws OperationCanceledException). The existing DbfDbConnection tests already call ExecuteReaderAsync/ReadAsync and now exercise the real override. Full suite: 119 passed, 1 skipped (pre-existing WIP skip).

🤖 Generated with Claude Code

Adds true async I/O to the read path. Each record is fetched with a
single buffered Stream.ReadAsync into the existing reused record
buffer and then parsed synchronously from memory by the existing
span-based value readers, shared with the synchronous path via
ParseRecord.

New API: DbfRecord.ReadAsync(Stream, CancellationToken),
DbfTable.ReadAsync(DbfRecord, CancellationToken),
DbfTable.ReadRecordAsync(CancellationToken), and a real
DbfDataReader.ReadAsync(CancellationToken) override (previously the
inherited DbDataReader default wrapped the synchronous Read).
ValueTask-based internals avoid per-record task allocations when
reads complete synchronously from the FileStream buffer, and the
cancellation token flows through to the underlying stream.

Memo values are still resolved synchronously while parsing; memo
blocks are small seeked reads and can be made async separately if
needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@chrisrichards
chrisrichards merged commit bbfab4e into main Jul 6, 2026
3 checks passed
@chrisrichards
chrisrichards deleted the feature/async-read branch July 6, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant