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

如何在C#中正确调用并关闭WPF窗体以避免插件崩溃

问题描述

为某应用开发了.NET 4.8版本插件,该应用同时支持.NET 4.8和.NET 8,两个版本下均出现相同问题:此前用WinForms开发插件无异常,迁移至WPF后,首次打开插件窗体正常,但关闭后再次打开时主应用崩溃,抛出错误:

无法访问插件信息 [调用线程无法访问此对象,因为另一个线程拥有它。]

从主应用错误日志判断,问题出在尝试打开新窗体实例时,之前的窗体或其部分资源未完全释放。以下两种打开WPF窗体的代码均会导致该崩溃:

版本一

public class SendConfigurationFormx
{
    public static void Create()
    {
        var thread = new Thread(() =>
        {
            var window = new ConfigFormWPF
            {
                WindowStartupLocation = WindowStartupLocation.CenterScreen
            };

            window.ShowDialog();  // 内部运行模态循环

        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        thread.Join();  // 等待窗口关闭后再继续
    }
}

版本二

public class SendConfigurationForm
{
    public static void Create()
    {
        var thread = new Thread(() =>
        {
            if (Application.Current == null)
            {
                new Application();
            }

            var window = new ConfigFormWPF
            {
                WindowStartupLocation = WindowStartupLocation.CenterScreen
            };

            window.ShowDialog(); // 若要无模态可使用Show(),但需要Dispatcher循环
        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        thread.Join();  // 等待窗口关闭后再继续
    }
}

无论是否保留thread.Join();,问题都无法解决。此前WinForms版本的代码无异常,代码如下:

public class SendConfigurationForm
{
    [STAThread]
    public static void Create()
    {
        if (!FormHelper.BringToFront("JanJan ProfileFile"))
        {
            Application.EnableVisualStyles();

            //Application.SetCompatibleTextRenderingDefault(false);
            var form = new ConfigurationForm();
            var location = new Point(Screen.AllScreens[0].Bounds.X + 1000, 500);

            form.Location = Config.GenFormLocation("ConfigDefaultMonitor");
            form.ShowDialog();

            // Application.Run(new ConfigurationForm());
        }
    }
}
解决方案

问题根源

  1. WPF线程绑定特性:WPF控件和窗口实例严格绑定到创建它们的STA线程,线程结束后若未正确清理Application对象或窗口资源,残留的Dispatcher线程资源会与新创建的线程/窗口产生冲突。
  2. 未终止WPF应用上下文:在独立线程中创建Application但未调用Shutdown(),导致Application.Current残留,后续创建新窗口时复用错误的线程上下文。

修正后的代码方案

方案一:确保线程内的Application正确关闭

在窗口关闭后显式调用Application.Shutdown(),彻底清理线程内的WPF上下文:

public class SendConfigurationForm
{
    public static void Create()
    {
        var thread = new Thread(() =>
        {
            var app = new Application();
            var window = new ConfigFormWPF
            {
                WindowStartupLocation = WindowStartupLocation.CenterScreen
            };
            
            // 窗口关闭时触发Application关闭
            window.Closed += (s, e) => app.Shutdown();
            
            app.Run(window); // 用app.Run替代ShowDialog,确保上下文正确管理
        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        thread.Join();
    }
}

方案二:复用主应用的Dispatcher(推荐,若主应用是WPF)

如果主应用本身是WPF应用,直接在主应用的UI线程上创建窗口,避免跨线程资源冲突:

public class SendConfigurationForm
{
    public static void Create()
    {
        // 极端情况:主应用无WPF上下文,回退到独立线程方案
        if (Application.Current == null)
        {
            var thread = new Thread(() =>
            {
                var app = new Application();
                var window = new ConfigFormWPF { WindowStartupLocation = WindowStartupLocation.CenterScreen };
                window.Closed += (s, e) => app.Shutdown();
                app.Run(window);
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
            thread.Join();
            return;
        }

        // 不在UI线程则调度到UI线程执行
        if (!Application.Current.Dispatcher.CheckAccess())
        {
            Application.Current.Dispatcher.Invoke(Create);
            return;
        }

        // 避免重复打开同一窗口实例
        var existingWindow = Application.Current.Windows.OfType<ConfigFormWPF>().FirstOrDefault();
        if (existingWindow != null)
        {
            existingWindow.Activate();
            return;
        }

        var window = new ConfigFormWPF { WindowStartupLocation = WindowStartupLocation.CenterScreen };
        window.ShowDialog();
    }
}

额外注意事项

  • 资源清理:在ConfigFormWPF的Closed事件中,手动解除所有事件订阅、释放非托管资源,避免内存泄漏和残留引用。
  • 跨线程操作:任何对窗口控件的后续操作必须通过Dispatcher.Invoke调度到创建窗口的线程执行,禁止跨线程直接访问控件。

内容的提问来源于stack exchange,提问作者dandan21

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 06:08:14