我们正在开发一个WPF应用程序,它将同时打开许多报告(就像典型的MDI应用程序,如Excel或Visual Studio)。虽然可以让这些报告的数据上下文在多个工作线程中运行,但我们仍然发现,如果打开的报告数量非常大,甚至是这些报告的呈现(基本上是在MDI环境中托管的UserControl,或者只是在主视图中的网格区域仍然会使应用程序响应较慢。
所以,我的想法是至少在主UI中有几个区域,每个区域都有其用户控件在不同的UI线程中运行。再次,想象一下视觉工作室中的典型视图,除了菜单之外,它具有文本编辑器的主要区域,存在例如解决方案资源管理器的侧面区域,以及承载例如错误列表和输出的底部区域。所以我希望这三个区域在三个UI线程中运行(但很自然它们托管在一个MainView中,这是我不确定的部分)。
我在问,因为我知道可以在不同的UI线程中运行几个(顶级)窗口。但是有人说它不适用于用户控件。这是真的吗?如果是这样,我的场景的典型解决方案是什么,即打开的UserControl的数量真的很大,而且其中许多UserControl都是实时的,因此渲染它们需要大量的资源?谢谢!
答案 0 :(得分:21)
通常,应用程序有一个“主”UI线程...它可能有0个或更多后台/工作者/非UI线程,您(或.NET运行时/框架)可以在后台工作。
(... WPF中有另一个特殊的线程称为渲染线程,但我现在暂时跳过它...)
例如,一个简单的WPF应用程序可能有这个线程列表:
一个简单的WinForms应用程序可能有这个线程列表:
创建元素时,它与特定Dispatcher
&绑定(具有亲和力)。线程,只能从与Dispatcher
相关联的线程安全访问。
如果您尝试从其他线程访问对象的属性或方法,通常会遇到异常,例如:在WPF中:
在WindowsForms中:
对UI的任何修改都需要在创建UI元素的同一个线程上执行...所以后台线程使用Invoke/BeginInvoke
来在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);
}
}
}
只要您将线程标记为使用STA公寓模型,并创建Dispatcher
(例如使用Dispatcher.Current
)并启动“运行”循环,就可以创建辅助UI线程(Dispatcher.Run()
)所以Dispatcher
可以为在该线程上创建的UI元素提供消息。
但是在一个UI线程中创建的元素无法放入 在另一个元素上创建的另一个元素的逻辑/可视树 UI线程。
有一种有限的解决方法技术,它可以为您提供一些能力来组合使用HostVisual
在不同线程中创建的可视树在一个UI线程中创建的元素的呈现。见这个例子:
答案 1 :(得分:1)
不,UserControls与UI线程相关联。即使您能够在其他地方初始化它们,您也会在将它们添加到主UI时遇到问题,因为它们属于不同的线程。
答案 2 :(得分:0)
您可以跨越不同的线程拆分可视树的渲染。
有关一个很好的解释和一个呈现视频输出的示例,请参阅此文章。 http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx
但是,当Visual的实际渲染在其他地方实现时,或者在WPF应用程序中技术上非常奇怪,例如将Direct3D场景渲染为Visual时,这样做才真正合理。
正如文章中提到的,这里的重要注意事项是,如果辅助线程呈现WPF XAML,则会丢失输入事件,因为路由事件不能越过线程边界。