2016-08-24 3 views
-1

Итак, скажем, у меня есть поток STA, работающий на фоне, и я создаю пользовательский элемент управления там.Каковы последствия создания элементов управления WPF в фоновом потоке?

Насколько функционально это будет? Каковы ограничения?

_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 файл. Я не вижу причины, почему я должен это делать в потоке пользовательского интерфейса.

+3

Почему, а почему вы, возможно, захотите это сделать? Кажется, что это самая большая вещь, которую нужно сделать ... –

+0

Почему вы хотите это сделать? Я считаю, что проблема возникнет, когда вы попытаетесь передать ее с помощью элементов управления для других потоков. Кроме того, он не получит обычные сообщения от насоса сообщений (например, обновление) – slawekwin

+3

Будет привязана привязка данных. Это может продолжаться до тех пор, пока на вашем контроле фактически не будет установлено все его свойства с помощью привязок, потому что шаблоны могут применяться и т. Д. Если вы вызываете делегата через диспетчер элемента управления, вызов будет выполняться в вашем потоке. Dispatcher.CurrentDispatcher должен возвращать тот же экземпляр, что и myControl.Dispatcher, потому что CurrentDispatcher вызывается в том же потоке, где был создан myControl. – Clemens

ответ

0

Я провел некоторое время тестирования вещи, и я думаю, что комментарий Clemens «s был точен. Ключевые моменты:

  1. myControl.Dispatcher и Dispatcher.CurrentDispatcher одно и то же, и содержать ссылку на диспетчеру фонового потока. Здесь нет сюрпризов.
  2. В целом элементы управления не будут корректно вести себя без запуска диспетчера, так как вызовы Dispatcher.BeginInvoke не обрабатываются. У вас есть два варианта. Либо позвоните Dispatcher.Run() на фоновом потоке и создать элементы управления с помощью вызываемой программы:

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

    или вручную push dispatcher frame каждый раз, когда вы хотите обработать диспетчер очередь и «обновить» свой контроль. Когда дело доходит до создания страниц XPS, оба подхода являются жизнеспособными.

  3. Связывание данных действительно работает, даже когда управление создается на фоне потока. Однако в некоторых случаях они не применяются мгновенно, и вам, возможно, придется подождать, пока диспетчер обработает свою очередь.

0

Вот пример приложения WPF, которое создает Окно в новом STA Thread. Я не вижу никаких проблем с этим. Я распечатал некоторые вещи: Название темы, ThreadId и Counter (изменения через 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])); 
     } 
    } 
} 
+0

У меня пока нет диспетчера. Я также не нуждаюсь в новом окне. –

+0

@NikitaB, простите мое невнимание. В вашей ситуации, когда вы создаете только usercontrol в другом потоке, эти ссылки могут быть полезны: [http://stackoverflow.com/questions/4884802/create-wpf-user-controls-in-other-thread](http : //stackoverflow.com/questions/4884802/create-wpf-user-controls-in-other-thread) и [Многопоточный интерфейс: HostVisual] (https://blogs.msdn.microsoft.com/dwayneneed/2007/04/26/многопоточный-щ-hostvisual /) –

Смежные вопросы