diff --git a/src/PostIt/PostIt/Services/UiDispatcher.cs b/src/PostIt/PostIt/Services/UiDispatcher.cs deleted file mode 100644 index e935ac1a..00000000 --- a/src/PostIt/PostIt/Services/UiDispatcher.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Threading.Tasks; -using Avalonia.Threading; - -namespace PostIt.Services; - -/// -/// Tiny marshalling helper around so -/// the rest of the codebase does not have to import Avalonia.Threading -/// directly. We want exactly one place that decides "is the current -/// thread the Avalonia UI thread, and if not, post there" so that -/// -derived types (Settings, the various -/// ViewModels) can fire PropertyChanged safely from background -/// work — which is exactly the cross-thread case that previously blew -/// up inside DataValidationErrors.SetErrors on Avalonia 11. -/// -/// The helper is intentionally tiny: a sync post when we are off the -/// UI thread, a no-op when we are already on it, and an async fire- -/// and-forget variant for places where awaiting would deadlock the -/// caller (e.g. Settings.Load continuation paths). -/// -public static class UiDispatcher -{ - /// - /// True when the calling thread is the Avalonia UI thread. Property - /// setters that touch bindings should check this before mutating - /// state; the safe path is . - /// - public static bool IsOnUiThread => Dispatcher.UIThread.CheckAccess(); - - /// - /// Run on the UI thread. If the caller is - /// already on the UI thread, run synchronously to preserve stack - /// traces and ordering; otherwise post to the dispatcher and wait. - /// Never throws on shutdown — a missing dispatcher is treated as - /// "best-effort skipped", matching Avalonia's own behaviour when - /// the application lifetime has been torn down. - /// - public static void InvokeIfNeeded(Action action) - { - if (action is null) return; - if (IsOnUiThread) { action(); return; } - try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } - catch (InvalidOperationException) { /* dispatcher gone, nothing to do */ } - } - - /// - /// Fire-and-forget variant: schedules on - /// the UI thread but does not block the caller. Use this from - /// background workers (OIDC discovery, HTTP callbacks, file I/O) - /// where awaiting the dispatcher would deadlock the calling sync - /// context. - /// - public static void Post(Action action) - { - if (action is null) return; - try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } - catch (InvalidOperationException) { /* dispatcher gone */ } - } - - /// - /// Awaitable variant. Useful inside async ViewModel methods - /// that must touch bindings only after the dispatcher has processed - /// a queued update (e.g. "load file then refresh observable state"). - /// - public static Task InvokeAsync(Action action) - { - if (action is null) return Task.CompletedTask; - if (IsOnUiThread) { action(); return Task.CompletedTask; } - return Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Normal).GetTask(); - } -}