Repository navigation
Add AsyncAction so async work is awaited, not run as async void [minor] - #78
Merged
Merged
Conversation
An async lambda assigned to Action compiles to async void and returns at its first await, so ActionTask completed early: runs overlapped, FromLastCompletion measured from the first await, and an exception after the await crashed the process. IntervalActionOptions.AsyncAction (Func<CancellationToken, Task>) is awaited, so a run lasts as long as its work, exceptions reach RethrowExceptions, and Stop() cancels the token. Setting both Action and AsyncAction is rejected. The README's long-running example now uses AsyncAction. Fixes #59 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MDVSpB69ZHnwPkg4LgXgcx
…t in the test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MDVSpB69ZHnwPkg4LgXgcx
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MDVSpB69ZHnwPkg4LgXgcx
3 tasks
…-59-async-action # Conflicts: # IntervalAction.Test/IntervalActionTests.cs
|
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #59
Problem
IntervalActionOptions.Actionis a plainAction. The README's long-running example assigns an async lambda to it, which compiles toasync void. That lambda returns at its firstawait, soActionTaskcompleted while the work was still running, which caused three problems:FromLastCompletionmeasured from the first await,Change
IntervalActionOptions.AsyncActionof typeFunc<CancellationToken, Task>?, following the triage comment.TryRunruns it throughRunAsyncAction, which awaits it. SoActionTaskcompletes when the work completes,FromLastCompletionis stamped after the await, and exceptions faultActionTaskand reachRethrowExceptions()like synchronous ones.Stop()cancels the running action's token. A run that ends withOperationCanceledExceptionbecause of that cancellation counts as a normal completion. The cancel happens outsideLockso the user's token callbacks never run under it. The run removes its source from the property underLockbefore disposing it, andStop()tolerates the dispose race.ActionorAsyncActionmay be set;StartthrowsArgumentExceptionif both are.Actionstill defaults to a no-op, held as one sharedNoActioninstance soStartcan tell the default apart from a value someone set.Action = nullis now allowed only whenAsyncActionis set. Every existing call site behaves as before.Actionhas a remarks warning against assigning async lambdas.AsyncAction, and the options table lists the new property.I tagged this
[minor]because it adds public API.Tests
These are the issue's acceptance criteria, plus two more:
AsyncActionRunsNeverOverlap: a 200 ms async body with a 20 ms interval never has more than one copy running.AsyncActionFromLastCompletionMeasuresFromWhenTheWorkFinished: each start is at leastActionIntervalafter the previous body's awaited work finished.AsyncActionExceptionAfterAwaitIsRethrown: an exception thrown after anawaitcomes out ofRethrowExceptions().StopCancelsTheAsyncActionToken:Stop()ends an hour-long, token-honouring run promptly, and the run doesn't fault.StartThrowsWhenBothActionAndAsyncActionAreSet.These tests can't compile against
main, because the API is new. To prove them, I temporarily replaced the awaited branch with the old fire-and-forget behaviour: start the task, don't await it. Under that mutation the three acceptance tests fail ("Async action runs should never overlap", "Run 1 started -46 ms after the previous run finished", "The polling loop should fault after the action throws"). With the real change the full suite passes, 26/26 locally on net10.0.This touches the same file as #76 and #77, but none of them depends on another. Whichever merges second may need a trivial merge.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MDVSpB69ZHnwPkg4LgXgcx
Generated by Claude Code