在后台线程上创建WPF控件有什么含义?

时间:2016-08-24 11:32:20

标签: c# wpf

所以,假设我有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线程上这样做。

2 个答案:

答案 0 :(得分:0)

以下是WPF应用程序的示例,该应用程序在新STA Thread中创建Window。我没有看到任何问题。我打印了一些东西:线程名称,ThreadId和Counter(通过INotifyPropertyChanged更改)。此外,我还从计时器Dispatcher.BeginInvoke更改了stackPanelCounter的背景。 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]));
        }
    }
}

答案 1 :(得分:0)

我花了一些时间测试一下,我认为 Clemens 的评论是准确的。要点是:

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

    _backgroundDispatcher.BeginInvoke(new Action(() => 
    {
       var myControl = new MyCustomControl();
       //do stuff
    })); 
    
    每次要处理调度程序队列时,

    或手动push dispatcher frame&#34;刷新&#34;你的控制。在构建XPS页面时,这两种方法都是可行的。

  3. 即使在后台线程上创建了控件,数据绑定也能正常工作。但是在某些情况下,它们不会立即应用,您可能需要等待调度员处理它的队列。