64c92c63e5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
124 lines
3.9 KiB
C#
124 lines
3.9 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Dedicated STA thread with a message pump for COM interop.
|
|
/// All COM operations are dispatched to this thread via a BlockingCollection.
|
|
/// </summary>
|
|
public sealed class StaDispatchThread : IDisposable
|
|
{
|
|
private static readonly ILogger Log = Serilog.Log.ForContext<StaDispatchThread>();
|
|
|
|
private readonly BlockingCollection<Action> _workQueue = new BlockingCollection<Action>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispatches an action to the STA thread and returns a Task that completes
|
|
/// when the action finishes.
|
|
/// </summary>
|
|
public Task DispatchAsync(Action action)
|
|
{
|
|
if (_disposed) throw new ObjectDisposedException(nameof(StaDispatchThread));
|
|
|
|
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
_workQueue.Add(() =>
|
|
{
|
|
try
|
|
{
|
|
action();
|
|
tcs.TrySetResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
tcs.TrySetException(ex);
|
|
}
|
|
});
|
|
return tcs.Task;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispatches a function to the STA thread and returns its result.
|
|
/// </summary>
|
|
public Task<T> DispatchAsync<T>(Func<T> func)
|
|
{
|
|
if (_disposed) throw new ObjectDisposedException(nameof(StaDispatchThread));
|
|
|
|
var tcs = new TaskCompletionSource<T>(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");
|
|
}
|
|
}
|
|
}
|