You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(hooks): auto-marshal UseState/UseReducer setters to UI thread (#212) (#242)
* fix(hooks): auto-marshal UseState/UseReducer setters to UI thread (#212)
UseState and UseReducer setters called from a background thread (Task.Run,
PeriodicTimer, callbacks after ConfigureAwait(false)) previously hit a
[Conditional("DEBUG")] AssertUIThread that either threw into a discarded
Task (DEBUG) or compiled out entirely (RELEASE), producing silent UI
update loss in both flavors.
Replace the DEBUG-only assert with an always-on UI-thread check that
auto-marshals the write and the resulting rerender onto the captured
ReactorApp.UIDispatcher when called off-thread. The hot path adds one
TLS read + int compare + branch (~1-2 ns); the marshal allocation is
only paid on the off-thread path, where it's swamped by cross-thread
dispatch cost. When no UI dispatcher has been captured (test/headless
contexts), the off-thread call now throws a loud InvalidOperationException
in both DEBUG and RELEASE instead of silently racing.
threadSafe: true retains its existing locked-in-place semantics for
callers that need many concurrent writers to serialize without an
intervening UI tick.
Adds a NonThreadSafeAutoMarshal selftest that mounts a real WinUI host
and reproduces the issue's Task.Run + PeriodicTimer pattern with default
hooks. Documents the auto-marshal contract in hooks.md and effects.md
(first mention of `threadSafe` in the guide).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* review: address PR #242 feedback (#212 follow-up)
- MarshalIfOffUIThread now throws InvalidOperationException when
DispatcherQueue.TryEnqueue returns false (dispatcher shutting down)
instead of silently dropping the state update.
- XML doc on MarshalIfOffUIThread reworded — dispatcher is captured
during host bootstrap (ReactorApp / ReactorHost / ReactorHostControl),
not specifically OnLaunched, so embedded and test-harness scenarios
are accurately described.
- NonThreadSafeAutoMarshal fixture now wraps the Task.Run loop in
try/catch + TrySetException and awaits done.Task under a 10s
Task.WhenAny timeout, so a regression that throws from the setter
fails the fixture deterministically instead of hanging the selftest
run.
- hooks.md / effects.md add the "captured dispatcher required" caveat
and document the shutdown / no-dispatcher InvalidOperationException
paths so the prose matches what the code actually does.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments