using XFESystem.Desktop; namespace XFESystem.Platform; /// One real managed thread owns its bounded FIFO. Runtime startup must verify threads, GC and EH before constructing it. public sealed class DesktopBackgroundWorkScheduler : IEditorBackgroundWorkScheduler, IDisposable { private const int Capacity = 64; private readonly object gate = new(); private readonly Queue pending = new(Capacity); private readonly Thread worker; private readonly Action? reportError; private bool stopping; private int workerThreadId; public int WorkerThreadId => Volatile.Read(ref workerThreadId); public DesktopBackgroundWorkScheduler(Action? reportError = null) { this.reportError = reportError; using var ready = new ManualResetEventSlim(); worker = new Thread(() => Run(ready)) { IsBackground = true }; worker.Start(); if (!ready.Wait(10_000)) throw new InvalidOperationException("The managed browser worker did not start."); if (WorkerThreadId == Environment.CurrentManagedThreadId) throw new InvalidOperationException("The browser worker shares the UI thread."); } public bool TryQueue(Action work) { ArgumentNullException.ThrowIfNull(work); lock (gate) { if (stopping || pending.Count == Capacity) return false; pending.Enqueue(work); Monitor.Pulse(gate); return true; } } private void Run(ManualResetEventSlim ready) { Volatile.Write(ref workerThreadId, Environment.CurrentManagedThreadId); ready.Set(); for (;;) { Action work; lock (gate) { while (pending.Count == 0 && !stopping) Monitor.Wait(gate); if (pending.Count == 0) return; work = pending.Dequeue(); } try { work(); } catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException) { reportError?.Invoke(error); } } } public void Dispose() { lock (gate) { stopping = true; Monitor.PulseAll(gate); } if (Environment.CurrentManagedThreadId != WorkerThreadId && !worker.Join(10_000)) throw new InvalidOperationException("The managed browser worker did not finish."); } }