在后台线程上创建WPF控件的含义是什么?
本文关键字:是什么 控件 WPF 后台 线程 创建 | 更新日期: 2023-09-27 18:03:57
那么,假设我有一个STA线程在后台运行,我在那里创建了一个用户控件。
它的功能将如何?限制是什么?
_workingThread = new Thread(() =>
{
//so far so good
var myControl = new MyCustomControl();
//what happens if i set DataContext? Will databinding work?
//It looks like it does, but I am not entirely sure.
myControl.DataContext = new MyViewModel();
//if databinding works, can I assume that at this point
//myControl's properties are already updated?
//what happens exactly if I invoke a delgate using Dispatcher property?
myControl.Dispatcher.Invoke(SomeMethod);
//or current dispatcher?
Dispatcher.CurrentDispatcher.BeginInvoke(SomeOtherMethod);
});
_workingThread.SetApartmentState(ApartmentState.STA);
_workingThread.Start();
要回答为什么这个问题:在。net中有一个叫做XpsDocument
的组件,它允许你将视觉效果写入xps
文件。我不知道为什么要在UI线程上做
这是一个WPF应用程序的例子,它在新的STA线程中创建窗口。我看不出有什么问题。我打印出一些东西:线程名称,ThreadId和计数器(通过INotifyPropertyChanged更改)。此外,我改变stackPanelCounter的背景从定时器Dispatcher.BeginInvoke。XAML:
<Window x:Class="WpfWindowInAnotherThread.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" Title="WPF: Windows and Threads">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<TextBlock Text="{Binding ThreadId, StringFormat='ThreadId: {0}'}" />
<TextBlock Text="{Binding ThreadName, StringFormat='ThreadName: {0}'}" />
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" Name="stackPanelCounter">
<TextBlock Text="Counter: " />
<TextBlock Text="{Binding Counter}" />
</StackPanel>
<StackPanel Grid.Row="2">
<Button Name="btnStartInNewThread" Content="Start window in new Thread"
Click="btnStartInNewThread_Click"/>
<Button Name="btnStartTheSameThread"
Content="Start window in the same Thread"
Click="btnStartTheSameThread_Click" />
</StackPanel>
</Grid>
</Window>
代码:using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
namespace WpfWindowInAnotherThread
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
static int _threadNumber = 0;
readonly Timer _timer;
int _Counter;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public int ThreadId
{
get { return Thread.CurrentThread.ManagedThreadId; }
}
public string ThreadName
{
get { return Thread.CurrentThread.Name; }
}
public int Counter
{
get { return _Counter; }
set { _Counter = value; PropertyChanged(this, new PropertyChangedEventArgs("Counter")); }
}
public MainWindow()
{
DataContext = this;
_timer = new Timer((o) => {
Counter++;
MainWindow wnd = o as MainWindow;
wnd.Dispatcher.BeginInvoke(new Action<MainWindow>(ChangeStackPanelBackground), wnd);
}, this, 0, 200);
InitializeComponent();
}
private void btnStartTheSameThread_Click(object sender, RoutedEventArgs e)
{
MainWindow mainWnd = new MainWindow();
mainWnd.Show();
}
private void btnStartInNewThread_Click(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(new ThreadStart(ThreadMethod));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
private static void ThreadMethod()
{
Thread.CurrentThread.Name = "MainWindowThread# " + _threadNumber.ToString();
Interlocked.Increment(ref _threadNumber);
MainWindow mainWnd = new MainWindow();
mainWnd.Show();
Dispatcher.Run();
}
private static void ChangeStackPanelBackground(MainWindow wnd)
{
Random rnd = new Random(Environment.TickCount);
byte[] rgb = new byte[3];
rnd.NextBytes(rgb);
wnd.stackPanelCounter.Background = new SolidColorBrush(Color.FromArgb(0xFF, rgb[0], rgb[1], rgb[2]));
}
}
}
我花了一些时间进行测试,我认为Clemens的评论是准确的。重点是:
-
myControl.Dispatcher
和Dispatcher.CurrentDispatcher
是同一个线程,都持有对后台线程调度程序的引用。没什么好惊讶的。 -
一般来说,如果没有调度程序运行,控件将无法正常运行,因为
Dispatcher.BeginInvoke
调用将不被处理。你有两个选择。要么在后台线程上调用Dispatcher.Run()
,然后使用invoke创建控件:_backgroundDispatcher.BeginInvoke(new Action(() => { var myControl = new MyCustomControl(); //do stuff }));
或者每次你想处理调度程序队列并"刷新"你的控件时手动推送调度程序帧。在构建XPS页面时,这两种方法都是可行的。
-
数据绑定确实有效,即使控件是在后台线程上创建的。然而,在某些情况下,它们不会立即应用,您可能需要等待调度程序处理它的队列。