using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Serilog; namespace ZB.MOM.WW.LmxProxy.Host.MxAccess { /// /// Dedicated STA thread with a message pump for COM interop. /// All COM operations are dispatched to this thread via a BlockingCollection. /// public sealed class StaDispatchThread : IDisposable { private static readonly ILogger Log = Serilog.Log.ForContext(); private readonly BlockingCollection _workQueue = new BlockingCollection(); private readonly Thread _staThread; private volatile bool _disposed; public StaDispatchThread(string threadName = "MxAccess-STA") { _staThread = new Thread(StaThreadLoop) { Name = threadName, IsBackground = true }; _staThread.SetApartmentState(ApartmentState.STA); _staThread.Start(); Log.Information("STA dispatch thread '{ThreadName}' started", threadName); } /// /// Dispatches an action to the STA thread and returns a Task that completes /// when the action finishes. /// public Task DispatchAsync(Action action) { if (_disposed) throw new ObjectDisposedException(nameof(StaDispatchThread)); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _workQueue.Add(() => { try { action(); tcs.TrySetResult(true); } catch (Exception ex) { tcs.TrySetException(ex); } }); return tcs.Task; } /// /// Dispatches a function to the STA thread and returns its result. /// public Task DispatchAsync(Func func) { if (_disposed) throw new ObjectDisposedException(nameof(StaDispatchThread)); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _workQueue.Add(() => { try { var result = func(); tcs.TrySetResult(result); } catch (Exception ex) { tcs.TrySetException(ex); } }); return tcs.Task; } private void StaThreadLoop() { Log.Debug("STA thread loop started"); // Process the work queue. GetConsumingEnumerable blocks until // items are available or the collection is marked complete. foreach (var action in _workQueue.GetConsumingEnumerable()) { try { action(); } catch (Exception ex) { // Should not happen — actions set TCS exceptions internally. Log.Error(ex, "Unhandled exception on STA thread"); } // Pump COM messages between work items Application.DoEvents(); } Log.Debug("STA thread loop exited"); } public void Dispose() { if (_disposed) return; _disposed = true; _workQueue.CompleteAdding(); // Wait for the STA thread to drain and exit if (_staThread.IsAlive && !_staThread.Join(TimeSpan.FromSeconds(10))) { Log.Warning("STA thread did not exit within 10 seconds"); } _workQueue.Dispose(); Log.Information("STA dispatch thread disposed"); } } }