Skip to content

Commit c3d6d99

Browse files
authored
fix: ensure consistent schedulers across threads by removing ThreadStatic in RxSchedulers (#4192)
### Summary Remove `ThreadStatic` from `RxSchedulers.MainThreadScheduler` and `RxSchedulers.TaskpoolScheduler` so that schedulers are **process-wide singletons** instead of **thread-local** values. ### Motivation / Context When `RxSchedulers` was introduced, both `MainThreadScheduler` and `TaskpoolScheduler` were marked `[ThreadStatic]`. Unlike the pre-split `RxApp` (where only the **unit test** fields were thread-static), this made the production schedulers **thread-local**. The result: * Accessing `RxSchedulers.MainThreadScheduler`/`TaskpoolScheduler` from different threads could return **different instances**. * Switching threads (or running in environments with multiple app domains/contexts) could cause the value to **“reset” back to defaults**, leading to subtle behavior changes and flakiness. This regressed the previous behavior where production schedulers were app-global, and only unit-test overrides were thread-scoped. ### What’s changed * Removed `[ThreadStatic]` from: ```csharp private static volatile IScheduler? _mainThreadScheduler; private static volatile IScheduler? _taskpoolScheduler; ``` * Retained the existing `lock`-based lazy init to ensure thread-safe, single-instance initialization: * `MainThreadScheduler` defaults to `DefaultScheduler.Instance`. * `TaskpoolScheduler` defaults to `TaskPoolScheduler.Default` (or `DefaultScheduler.Instance` in `PORTABLE`). * No public API changes. ### Current behavior (before this PR) * `RxSchedulers.MainThreadScheduler` / `TaskpoolScheduler` are **thread-local**: each thread can see an independent value, and new threads may “re-initialize” to default unexpectedly. * Tests or apps that set the scheduler on one thread can observe a **different scheduler** on other threads. ### New behavior (after this PR) * `RxSchedulers.MainThreadScheduler` / `TaskpoolScheduler` are **process-wide singletons**: * Set once, visible consistently across all threads. * No unexpected resets when code runs on a different thread. * `RxApp` semantics remain unchanged: * Unit test overrides are still isolated via thread-static fields **inside `RxApp` only**, preserving historical test behavior. ### Risks / Breaking changes * Very low. The change restores the pre-split behavior and aligns `RxSchedulers` with developer expectations (global, stable schedulers in production code). * Code that **relied** (intentionally or accidentally) on the thread-local behavior of `RxSchedulers` may observe different (correct) behavior now; this is considered a bug fix. ### How I verified * Manual sanity checks: * Set `RxSchedulers.MainThreadScheduler` on a background thread; read from UI/main thread → **same instance**. * Spawn multiple threads reading/writing schedulers concurrently → stable value, no races (protected by `lock`). * Ensured `RxApp` unit-test behavior remains intact (thread-static kept where it was originally: `_unitTest*` fields). ### Repro (old bug) ```csharp // On thread A RxSchedulers.MainThreadScheduler = new TestScheduler(); // On thread B var s = RxSchedulers.MainThreadScheduler; // Before: could be DefaultScheduler (reset). After: TestScheduler. ``` ### Documentation impact * None for public API. Internal behavior now matches historical RxUI expectations: * Use `RxSchedulers` for simple, app-global schedulers. * Use `RxApp` when you need test-runner detection and per-thread unit test overrides. ### Related * Regression was introduced when splitting schedulers into `RxSchedulers`: production fields gained `[ThreadStatic]`, diverging from the original `RxApp` pattern where only **unit test** fields were thread-static. ### Checklist * [x] Bug fix (no breaking public API changes) * [x] Behavior aligned with pre-split `RxApp` * [ ] Tests added:
1 parent 61cfabc commit c3d6d99

File tree

1 file changed

+0
-2
lines changed

1 file changed

+0
-2
lines changed

src/ReactiveUI/RxSchedulers.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ public static class RxSchedulers
2121
{
2222
private static readonly object _lock = new();
2323

24-
[ThreadStatic]
2524
private static volatile IScheduler? _mainThreadScheduler;
2625

27-
[ThreadStatic]
2826
private static volatile IScheduler? _taskpoolScheduler;
2927

3028
/// <summary>

0 commit comments

Comments
 (0)