using XFESystem.Desktop;
namespace XFESystem.Platform;
/// <summary>One real managed thread owns its bounded FIFO. Runtime startup must verify threads, GC and EH before constructing it.</summary>
public sealed class DesktopBackgroundWorkScheduler : IEditorBackgroundWorkScheduler, IDisposable
{
private const int Capacity = 64;
private readonly object gate = new();
private readonly Queue<Action> pending = new(Capacity);
private readonly Thread worker;
private readonly Action<Exception>? reportError;
private bool stopping;
private int workerThreadId;
public int WorkerThreadId => Volatile.Read(ref workerThreadId);
public DesktopBackgroundWorkScheduler(Action<Exception>? 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.");
}
}
using XFESystem.Desktop;
namespace XFESystem.Platform;
/// <summary>One real managed thread owns its bounded FIFO. Runtime startup must verify threads, GC and EH before constructing it.</summary>
public sealed class DesktopBackgroundWorkScheduler : IEditorBackgroundWorkScheduler, IDisposable
{
private const int Capacity = 64;
private readonly object gate = new();
private readonly Queue<Action> pending = new(Capacity);
private readonly Thread worker;
private readonly Action<Exception>? reportError;
private bool stopping;
private int workerThreadId;
public int WorkerThreadId => Volatile.Read(ref workerThreadId);
public DesktopBackgroundWorkScheduler(Action<Exception>? 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.");
}
}