You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

设置ApartmentState.STA运行NUnit测试时触发ThreadAbortException求助

解决WPF窗口NUnit测试中的ThreadAbortException问题

我来帮你拆解一下这个ThreadAbortException的根源,然后一步步给出修复方案:

问题根源分析

  1. 强制终止Dispatcher触发线程中止:你在测试代码的窗口Closed事件里调用了userPromptWPS.Dispatcher.InvokeShutdown(),这个方法会直接终止UI线程的Dispatcher循环,进而引发ThreadAbortException强制结束STA线程。实际上ShowDialog()本身会阻塞直到窗口关闭,完全不需要手动关闭Dispatcher。
  2. 无取消机制的无限循环:后台任务是一个无限while(true)循环,如果ClickWPS一直返回false,循环永远不会停止,不仅浪费资源,还会导致窗口无法正常关闭,间接加剧线程异常。
  3. 结果属性未正确赋值:目前代码里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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 18:45:49