在后台线程上创建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控件的含义是什么?

这是一个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的评论是准确的。重点是:

  1. myControl.DispatcherDispatcher.CurrentDispatcher是同一个线程,都持有对后台线程调度程序的引用。没什么好惊讶的。
  2. 一般来说,如果没有调度程序运行,控件将无法正常运行,因为Dispatcher.BeginInvoke调用将不被处理。你有两个选择。要么在后台线程上调用Dispatcher.Run(),然后使用invoke创建控件:

    _backgroundDispatcher.BeginInvoke(new Action(() => 
    {
       var myControl = new MyCustomControl();
       //do stuff
    })); 
    

    或者每次你想处理调度程序队列并"刷新"你的控件时手动推送调度程序帧。在构建XPS页面时,这两种方法都是可行的。

  3. 数据绑定确实有效,即使控件是在后台线程上创建的。然而,在某些情况下,它们不会立即应用,您可能需要等待调度程序处理它的队列。