是否可以在不同的线程中初始化WPF用户控件?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (98)

我们正在开发一个WPF应用程序,它将同时打开许多报表(就像典型的MDI应用程序,如Excel或VisualStudio)。尽管可以在多个工作线程中运行这些报告的数据上下文,但我们仍然发现,如果打开的报告数量确实很大,即使呈现这些报告(主要是在MDI环境中或仅仅在主视图中的网格区域中托管的UserControl)仍然会降低应用程序的响应能力。

因此,我的想法是在主UI中至少有几个区域,每个区域将在不同的UI线程中运行其用户控件。再一次,想象一下Visualstudio中的一个典型视图,除了菜单之外,它有文本编辑器的主区域、承载解决方案资源管理器的边区域,以及包含例如错误列表和输出的底部区域。因此,我希望这三个区域在三个UI线程中运行(当然,它们是托管在一个MainView中的,这是我不确定的部分)。

我之所以问这个问题,是因为我知道,在不同的UI线程中运行几个(顶级)窗口是可能的。但有人说它不适用于用户控件。是真的吗?如果是的话,我的场景的典型解决方案是什么,即打开的UserControl的数量确实很大,而且这些UserControl中的许多是实时的,因此呈现它们需要大量的资源。谢谢!

提问于
用户回答回答于

UI线程模型的背景信息

通常,应用程序有一个“主”UI线程...而且它可能有0或更多的背景/工作者/非UI线程,您(或.NET运行时/框架)在其中执行后台工作。

例如,简单的WPF应用程序可能有以下线程列表:

简单的WinForms应用程序可能有以下线程列表:

创建元素时,它与特定的Dispatcher&线程,并且只能从与Dispatcher

如果尝试从其他线程访问对象的属性或方法,通常会得到异常,例如在WPF中:

在WindowsForms中:

对UI的任何修改都需要在创建UI元素的同一个线程上执行。所以后台线程使用Invoke/BeginInvoke要在UI线程上运行该工作,请执行以下操作。

演示演示在非ui线程上创建元素的问题。

<Window x:Class="WpfApplication9.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
    <StackPanel x:Name="mystackpanel">

    </StackPanel>
</Window>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;

namespace WpfApplication9
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Thread m_thread1;
        Thread m_thread2;
        Thread m_thread3;
        Thread m_thread4;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            CreateAndAddElementInDifferentWays();
        }

        void CreateAndAddElementInDifferentWays()
        {
            string text = "created in ui thread, added in ui thread [Main STA]";
            System.Diagnostics.Debug.WriteLine(text);

            CreateAndAddTextChild(text);

            // Do NOT use any Joins with any of these threads, otherwise you will get a
            // deadlock on any "Invoke" call you do.

            // To better observe and focus on the behaviour when creating and
            // adding an element from differently configured threads, I suggest
            // you pick "one" of these and do a recompile/run.

            ParameterizedThreadStart paramthreadstart1 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnThread);
            m_thread1 = new Thread(paramthreadstart1);
            m_thread1.SetApartmentState(ApartmentState.STA);
            m_thread1.Start("[STA]");

            //ParameterizedThreadStart paramthreadstart2 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnUIThread);
            //m_thread2 = new Thread(paramthreadstart2);
            //m_thread2.SetApartmentState(ApartmentState.STA);
            //m_thread2.Start("[STA]");

            //ParameterizedThreadStart paramthreadstart3 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnThread);
            //m_thread3 = new Thread(paramthreadstart3);
            //m_thread3.SetApartmentState(ApartmentState.MTA);
            //m_thread3.Start("[MTA]");

            //ParameterizedThreadStart paramthreadstart4 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnUIThread);
            //m_thread4 = new Thread(paramthreadstart4);
            //m_thread4.SetApartmentState(ApartmentState.MTA);
            //m_thread4.Start("[MTA]");
        }

        //----------------------------------------------------------------------

        void WorkCreatedOnThreadAddedOnThread(object parameter)
        {
            string threadingmodel = parameter as string;

            string text = "created in worker thread, added in background thread, " + threadingmodel;
            System.Diagnostics.Debug.WriteLine(text);

            CreateAndAddTextChild(text);
        }

        void WorkCreatedOnThreadAddedOnUIThread(object parameter)
        {
            string threadingmodel = parameter as string;

            string text = "created in worker thread, added in ui thread via invoke" + threadingmodel;
            System.Diagnostics.Debug.WriteLine(text);

            TextBlock tb = CreateTextBlock(text);
            if (tb != null)
            {
                // You can alternatively use .Invoke if you like!

                DispatcherOperation dispop = Dispatcher.BeginInvoke(new Action(() =>
                {
                    // Get this work done on the main UI thread.

                    AddTextBlock(tb);
                }));

                if (dispop.Status != DispatcherOperationStatus.Completed)
                {
                    dispop.Wait();
                }
            }
        }

        //----------------------------------------------------------------------

        public TextBlock CreateTextBlock(string text)
        {
            System.Diagnostics.Debug.WriteLine("[CreateTextBlock]");

            try
            {
                TextBlock tb = new TextBlock();
                tb.Text = text;
                return tb;
            }
            catch (InvalidOperationException ex)
            {
                // will always exception, using this to highlight issue.
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }

            return null;
        }

        public void AddTextBlock(TextBlock tb)
        {
            System.Diagnostics.Debug.WriteLine("[AddTextBlock]");

            try
            {
                mystackpanel.Children.Add(tb);
            }
            catch (InvalidOperationException ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }
        }

        public void CreateAndAddTextChild(string text)
        {
            TextBlock tb = CreateTextBlock(text);
            if (tb != null)
                AddTextBlock(tb);
        }
    }
}

辅助用户界面线程,即“在另一个线程上创建顶层窗口”。

创建辅助UI线程是可能的,只要你使用STA单元模型将线程标记为,并创建一个Dispatcher(例如使用)Dispatcher.Current)并启动“Run”循环(Dispatcher.Run())所以Dispatcher可以为在该线程上创建的UI元素提供消息服务。

但是,在一个UI线程中创建的元素不能放在另一个元素的逻辑/可视树中,后者是在另一个UI线程上创建的。

用户回答回答于

不对,当你把UserControls绑定到UI线程,即使你能够在其他地方初始化它们,也会遇到将它们添加到主UI中的问题,因为它们属于不同的线程。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励