【问题标题】:How do I create and show WPF windows on separate threads?如何在单独的线程上创建和显示 WPF 窗口?
【发布时间】:2020-10-21 13:20:06
【问题描述】:

我需要从同一个进程创建两个(或更多)WPF 窗口。但是窗口必须由单独的线程处理,因为它们不应该能够相互阻塞。我该怎么做?

在 WinForms 中,这是通过以下方式实现的:

  • 开始一个新线程
  • 从新线程创建表单
  • 以表单为参数调用 Application.Run

但是我如何在 WPF 中做同样的事情呢?

【问题讨论】:

    标签: wpf multithreading


    【解决方案1】:

    正如msdn 所说:

    private void NewWindowHandler(object sender, RoutedEventArgs e)
    {       
        Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
        newWindowThread.SetApartmentState(ApartmentState.STA);
        newWindowThread.IsBackground = true;
        newWindowThread.Start();
    }
    
    private void ThreadStartingPoint()
    {
        Window1 tempWindow = new Window1();
        tempWindow.Show();       
        System.Windows.Threading.Dispatcher.Run();
    }
    

    编辑: 这是一个旧答案,但由于它似乎经常被访问,我还可以考虑以下修改/改进(未测试)。

    如果您想关闭这样的窗口,只需从线程外部(委托)保留对 Window 对象的引用,然后对其调用 close,如下所示:

    void CloseWindowSafe(Window w)
    {
        if (w.Dispatcher.CheckAccess())
            w.Close();
        else
            w.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(w.Close));
    }
    
    // ...
    CloseWindowSafe(tempWindow);
    

    如果新线程可以终止(强制中止),符合 cmets 中的问题:

    private void ThreadStartingPoint()
    {
        try{
            Window1 tempWindow = new Window1();
            tempWindow.Show();       
            System.Windows.Threading.Dispatcher.Run();
        }
        catch(ThreadAbortException)
        {
            tempWindow.Close();
            System.Windows.Threading.Dispatcher.InvokeShutdown();
        }
        //the CLR will "rethrow" thread abort exception automatically
    }
    

    免责声明:不要在家里这样做,中止线程(几乎总是)违反最佳做法。应该通过各种同步技术中的任何一种来优雅地处理线程,或者在这种情况下,只需通过调用的window.Close()

    【讨论】:

    • 太棒了。如何关闭以这种方式创建的窗口?
    • 我知道这是一个老问题,但这种方法仍然有效。我只有一个问题 - 我用它来加载窗口所以 - 在新线程中显示加载窗口 -> 做事 -> newThread.Abort() 没关系,但如果我再做一次,它会使整个应用程序崩溃。可能是因为它将再次运行调度程序。如何解决?
    • 嗨,我也在为此苦苦挣扎。对我来说,这个link 有效。这使用 ManualResetEvent 方式。也有源码可以下载查看(GitHub)
    • 根据我的经验,您无法安全地捕捉到ThreadAbortException。最好你删除答案的那部分。 Dispatcher.Invoke + Window.Close 的方法更安全。
    【解决方案2】:

    不管它值多少钱,因为这个答案是谷歌提供的第一个结果。我还想添加以下来自Eugene Prystupa's Weblog的内容:

    “我们的简化解决方案有一个问题。关闭特定窗口并不会终止此窗口的线程调度程序,因此线程继续运行,并且在关闭所有窗口后,进程不会终止并且将成为一个幽灵进程。[简单(但不正确)的解决方案是将我们的线程标记为后台(使用 thread.IsBackground = true;)。这将在主 UI 线程终止时强制它们终止。

    正确的实现将在不再需要调度程序时优雅地关闭它。下面的代码是在窗口关闭时终止调度程序的策略示例:"

    private void OnCreateNewWindow(object sender, RoutedEventArgs e)
    {
        Thread thread = new Thread(() =>
        {
            Window1 w = new Window1();
            w.Show();
    
            w.Closed += (sender2, e2) =>
                w.Dispatcher.InvokeShutdown();
    
            System.Windows.Threading.Dispatcher.Run();
        });
            
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
    }
    

    【讨论】:

    【解决方案3】:

    正是我想要的。

    虽然我是这样做的:

    App.xaml.cs

        public partial class App : Application
        {
            protected override void OnStartup(StartupEventArgs e)
            {
                base.OnStartup(e);
    
                Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
                newWindowThread.SetApartmentState(ApartmentState.STA);
                newWindowThread.IsBackground = true;
                newWindowThread.Start();
    
                var window = new MainWindow(newWindowThread);
                window.DataContext = new MainWindowViewModel(window);
                window.Show();
            }
    
            private void ThreadStartingPoint()
            {
                SplashWindow tempWindow = new SplashWindow();
                tempWindow.Show();
                System.Windows.Threading.Dispatcher.Run();
            }
        }
    

    MainWindow.Xaml.cs

    public partial class MainWindow : Window
    {
        public MainWindow(Thread splashWindowThread)
        {
            InitializeComponent();
    
            MyInializaComponent();
    
            splashWindowThread.Abort();
        }
    
    //void DoStuff(){};
    }
    

    程序完成所有加载后,我需要我的启动画面消失。

    【讨论】:

      【解决方案4】:

      我想我找到了答案。看看 Jon Skeet 在这个 question 中的回答。

      基本上你在你的线程启动方法中这样做:

      private void ThreadStartingPoint()
      {
          Window1 tempWindow = new Window1();
          tempWindow.Show();       
          System.Windows.Threading.Dispatcher.Run();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-09-03
        • 1970-01-01
        • 2019-08-27
        • 1970-01-01
        • 2015-08-30
        • 2013-05-22
        • 2013-08-19
        相关资源
        最近更新 更多