设置ApartmentState.STA运行NUnit测试时触发ThreadAbortException求助
解决WPF窗口NUnit测试中的ThreadAbortException问题
我来帮你拆解一下这个ThreadAbortException的根源,然后一步步给出修复方案:
问题根源分析
- 强制终止Dispatcher触发线程中止:你在测试代码的窗口
Closed事件里调用了userPromptWPS.Dispatcher.InvokeShutdown(),这个方法会直接终止UI线程的Dispatcher循环,进而引发ThreadAbortException强制结束STA线程。实际上ShowDialog()本身会阻塞直到窗口关闭,完全不需要手动关闭Dispatcher。 - 无取消机制的无限循环:后台任务是一个无限
while(true)循环,如果ClickWPS一直返回false,循环永远不会停止,不仅浪费资源,还会导致窗口无法正常关闭,间接加剧线程异常。 - 结果属性未正确赋值:目前代码里
WPSButtonPassed属性没有在ClickWPS返回true时被赋值,测试最后拿到的永远是默认的false,测试逻辑本身也会失效。
修复方案及代码修改
1. 重构UserPromptWPS窗口代码,添加任务取消机制
给后台任务加上CancellationToken,确保窗口关闭时能终止循环,同时正确设置结果属性:
public partial class UserPromptWPS : Window { public bool WPSButtonFailed { get; set; } public bool WPSButtonPassed { get; set; } public bool DoTest { get; set; } private IWebDriver driver; private WebDriverWait wait; private CancellationTokenSource _cts; public UserPromptWPS(IWebDriver driver, WebDriverWait wait) { InitializeComponent(); this.driver = driver; this.wait = wait; _cts = new CancellationTokenSource(); Loaded += UserPromptWPS_Loaded; Closed += UserPromptWPS_Closed; // 绑定窗口关闭事件,用于取消任务 } private async void UserPromptWPS_Loaded(object sender, RoutedEventArgs e) { WPSButton wpsButton = new WPSButton(); try { await Task.Run(() => { while (!_cts.Token.IsCancellationRequested) { if(wpsButton.ClickWPS(driver,wait)) { // 点击成功,在UI线程更新结果属性 Dispatcher.Invoke(() => WPSButtonPassed = true); break; } // 添加短间隔,避免CPU占用过高 Thread.Sleep(100); } }, _cts.Token); } catch (OperationCanceledException) { // 任务被取消时标记失败 Dispatcher.Invoke(() => WPSButtonFailed = true); } finally { // 确保窗口正常关闭 Dispatcher.Invoke(() => Close()); } } private void UserPromptWPS_Closed(object sender, EventArgs e) { // 窗口关闭时取消后台任务并释放资源 _cts.Cancel(); _cts.Dispose(); } }
2. 修改NUnit测试代码,移除Dispatcher.InvokeShutdown
去掉会导致线程中止的代码,让窗口正常关闭后再获取结果:
[TestFixture] public class WPSButtonTests { [Test] public void ExecuteClickWPS_Success_ReturnsTrue() { var result = false; Thread thread = new Thread(new ThreadStart(delegate { LoginWebGui loginWebGui = new LoginWebGui(); ClearLog clearLog = new ClearLog(); WPSButton wpsButton = new WPSButton(); LogoutWebGui logoutWebGui = new LogoutWebGui(); var webDriverSetup = new WebDriverSetup(); var driver = webDriverSetup.WebDriver; var wait = webDriverSetup.Wait; loginWebGui.ExecuteLogin(driver, wait); clearLog.ExecuteClearLog(driver, wait); UserPromptWPS userPromptWPS = new UserPromptWPS(driver, wait); // 移除Dispatcher.InvokeShutdown,ShowDialog会自动等待窗口关闭 userPromptWPS.ShowDialog(); result = userPromptWPS.WPSButtonPassed; // 测试结束后清理WebDriver资源 logoutWebGui.ExecuteLogout(driver, wait); driver.Quit(); })); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); Assert.That(result, Is.True); } }
额外注意事项
- 避免不必要的async void:虽然WPF事件处理程序允许用
async void,但如果场景允许,尽量用async Task结合自定义事件/命令,这样更便于处理异常和任务状态。 - WebDriver资源清理:测试结束后一定要调用
driver.Quit(),避免残留浏览器进程占用资源。 - 循环间隔优化:给循环添加短间隔,避免持续占用过高CPU。
内容的提问来源于stack exchange,提问作者joekevinrayan96
相关产品推荐
相关产品推荐

