Files
autodesk/DocumentAutomation/src/DocxWorkbench.WordInterop/StaWordQueue.cs
T

61 lines
2.4 KiB
C#

using System.Collections.Concurrent;
namespace DocxWorkbench.WordInterop;
internal sealed class StaWordQueue : IDisposable
{
private readonly BlockingCollection<Action> _jobs = new();
private readonly Thread _thread;
private readonly ManualResetEventSlim _stopped = new(false);
private readonly object _lifetimeGate = new();
private bool _accepting = true;
public StaWordQueue()
{
_thread = new Thread(Run) { IsBackground = true, Name = "DocumentAutomation.Word.STA" };
_thread.SetApartmentState(ApartmentState.STA);
_thread.Start();
}
public Task<T> Enqueue<T>(Func<T> operation, CancellationToken cancellationToken)
{
if (operation is null) throw new ArgumentNullException(nameof(operation));
if (cancellationToken.IsCancellationRequested) return Task.FromCanceled<T>(cancellationToken);
var completion = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
lock (_lifetimeGate)
{
if (!_accepting) throw new ObjectDisposedException(nameof(StaWordQueue));
try { _jobs.Add(() => Execute(operation, completion, cancellationToken), cancellationToken); }
catch (InvalidOperationException) { throw new ObjectDisposedException(nameof(StaWordQueue)); }
catch (OperationCanceledException) { completion.TrySetCanceled(cancellationToken); }
}
return completion.Task;
}
private static void Execute<T>(Func<T> operation, TaskCompletionSource<T> completion, CancellationToken cancellationToken)
{
try { cancellationToken.ThrowIfCancellationRequested(); completion.TrySetResult(operation()); }
catch (OperationCanceledException ex) { completion.TrySetCanceled(ex.CancellationToken); }
catch (Exception ex) { completion.TrySetException(ex); }
}
private void Run()
{
try { foreach (var job in _jobs.GetConsumingEnumerable()) job(); }
finally { _stopped.Set(); }
}
public void Dispose()
{
lock (_lifetimeGate)
{
if (!_accepting) return;
_accepting = false;
_jobs.CompleteAdding();
}
if (Thread.CurrentThread != _thread) _stopped.Wait(TimeSpan.FromSeconds(30));
// Do not dispose the collection or stop event while the STA worker may
// still be draining it. The worker owns the final enumeration lifetime.
}
}