Skip to content

Run InvokeAsync and TryBeginInvoke work in submission order [patch] - #80

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/issue-60-submission-order
Oct 9, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
claude/issue-60-submission-order

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Summary

DoInvokes emptied the whole TryBeginInvoke queue before it touched the task queue. A thread that called InvokeAsync(A) and then TryBeginInvoke(B) therefore saw B run first. This implements the maintainer decision on the issue: keep submission order across both APIs.

  • Shared submission number. Each queued item, on either queue, gets a number from one Interlocked.Increment counter before it is enqueued.
  • Merged drain. DoInvokes merges the two queues by always running whichever head has the lower number.
    • Each queue is FIFO, and a thread takes its number before it enqueues. So a single thread's work runs in the order that thread submitted it, whichever API it used.
  • Still allocation-free. TryBeginInvoke stores the number in the existing pre-allocated cell as a value tuple, so it allocates nothing new.
  • Behavior otherwise unchanged:
    • Each pump still runs only the work that was queued when it started (per-queue budgets).
    • BeginInvokeFailed and the AggregateException work as before.
    • Tasks still run inline on TaskScheduler.Default.
  • Docs.
    • The TryBeginInvoke and DoInvokes remarks now state the ordering guarantee across both APIs, and CLAUDE.md describes the merge.
    • The TryBeginInvoke summary had been cut off mid-sentence ("Attempts to queue an action / or allocating"), and is now complete.

Tests

Two new tests in TryBeginInvokeTests:

  • InvokeAsyncThenTryBeginInvokeFromOneThreadRunInSubmissionOrder is the issue's acceptance test.
  • InterleavedSubmissionsFromOneThreadRunInSubmissionOrder mixes both APIs in the order B1 A1 A2 B2 B3 A3.

Results:

  • Both tests fail on main, which produces B A and B1 B2 B3 A1 A2 A3.
  • Both pass with this change.
  • The full suite passes 39/39, and passed on three repeat runs.
  • dotnet build -c Release builds every target framework.

Fixes #60

🤖 Generated with Claude Code

https://claude.ai/code/session_01CTnJ1xEhZzPB9o25LiPf2C


Generated by Claude Code

DoInvokes drained the whole TryBeginInvoke queue before the task queue,
so a thread that called InvokeAsync(A) and then TryBeginInvoke(B) saw B
run first, and a follow-up could use a resource before it existed.

Stamp every queued item, on both queues, with a shared
Interlocked.Increment submission number taken before it is enqueued,
and have DoInvokes merge the two queues by taking the lower-numbered
head each time. TryBeginInvoke stays allocation-free: the number rides
in the existing pre-allocated cell as a value tuple. Per-pump budgets,
exception collection and inline task scheduling are unchanged.

Document the cross-API ordering guarantee on TryBeginInvoke and
DoInvokes, and repair the truncated TryBeginInvoke summary.

Fixes #60

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTnJ1xEhZzPB9o25LiPf2C
@sonarqubecloud

sonarqubecloud Bot commented Oct 8, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit c62a68c into main Oct 9, 2026
14 checks passed
@matt-edmondson
matt-edmondson deleted the claude/issue-60-submission-order branch October 9, 2026 01:15
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.

DoInvokes runs TryBeginInvoke actions before earlier InvokeAsync work from the same thread, so submission order isn't preserved

2 participants