using System.Collections.Concurrent;
using AxiOmron.PcbCheck.Utils;
using Microsoft.Extensions.Hosting;
using Xunit;
namespace AxiOmron.PcbCheck.Tests;
///
/// 验证应用退出阶段的异步清理流程不会丢失原始线程上下文。
///
public sealed class ShutdownHelperTests
{
///
/// 停止 Host 后,后续收尾回调应继续在捕获到的同步上下文线程上执行。
///
/// 异步测试任务。
[Fact]
public async Task StopHostAsync_ShouldResumeOnCapturedSynchronizationContext()
{
await SingleThreadSynchronizationContext.RunAsync(async () =>
{
int expectedThreadId = Environment.CurrentManagedThreadId;
int callbackThreadId = -1;
var host = new FakeHost();
await ShutdownHelper.StopHostAsync(
host,
TimeSpan.FromMilliseconds(200),
() => callbackThreadId = Environment.CurrentManagedThreadId);
Assert.True(host.StopAsyncCalled);
Assert.Equal(expectedThreadId, callbackThreadId);
});
}
private sealed class FakeHost : IHost
{
public IServiceProvider Services => throw new NotSupportedException();
public bool StopAsyncCalled { get; private set; }
public void Dispose()
{
}
public Task StartAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken = default)
{
StopAsyncCalled = true;
await Task.Yield();
}
}
///
/// 提供仅在单线程内串行泵送回调的同步上下文,便于验证 await 后的线程恢复行为。
///
private sealed class SingleThreadSynchronizationContext : SynchronizationContext
{
private readonly BlockingCollection<(SendOrPostCallback Callback, object? State)> _queue = new();
///
/// 在专用线程同步上下文中执行异步委托。
///
/// 待执行的异步委托。
/// 表示执行完成的任务。
public static Task RunAsync(Func action)
{
ArgumentNullException.ThrowIfNull(action);
var completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var thread = new Thread(() =>
{
var context = new SingleThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(context);
Task task;
try
{
task = action();
}
catch (Exception ex)
{
completionSource.SetException(ex);
return;
}
task.ContinueWith(
completedTask =>
{
if (completedTask.IsFaulted)
{
completionSource.SetException(completedTask.Exception!.InnerExceptions);
}
else if (completedTask.IsCanceled)
{
completionSource.SetCanceled();
}
else
{
completionSource.SetResult();
}
context._queue.CompleteAdding();
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.Default);
foreach ((SendOrPostCallback callback, object? state) in context._queue.GetConsumingEnumerable())
{
callback(state);
}
})
{
IsBackground = true
};
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return completionSource.Task;
}
///
/// 将委托异步排入当前上下文队列。
///
/// 待执行回调。
/// 状态对象。
public override void Post(SendOrPostCallback d, object? state)
{
ArgumentNullException.ThrowIfNull(d);
_queue.Add((d, state));
}
///
/// 在当前线程直接执行同步回调。
///
/// 待执行回调。
/// 状态对象。
public override void Send(SendOrPostCallback d, object? state)
{
ArgumentNullException.ThrowIfNull(d);
d(state);
}
}
}