-1

So, lets say I have STA thread running on background and I create a user control there.

How functional it is going to be? What are the limitations?

_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();

To answer the question why: there is a component in .Net called XpsDocument which allows you to write visuals into xps file. I don't see a reason, why I should do it on UI thread.

Nikita B
  • 3,303
  • 1
  • 23
  • 41
  • 3
    Why, oh why would you possibly want to do this? It seems the utterly most backwards thing to do... – Immortal Blue Aug 24 '16 at 11:35
  • Why do you want to do this? I believe the problem will arise when you try to communicate it with controls on other threads. Also, it will not receive normal messages from the message pump (like refresh) – slawekwin Aug 24 '16 at 11:36
  • 3
    Data binding will work. It may however last until your control is actually shown to set all its properties by bindings, because templates may have to applied etc. If you invoke a delegate via the control's Dispatcher, the invocation will be made in your thread. Dispatcher.CurrentDispatcher should return the same instance as myControl.Dispatcher, because CurrentDispatcher is called in the same thread where myControl was created. – Clemens Aug 24 '16 at 11:41
  • @ImmortalBlue, I've edited my question. Hopefully I satisfied your curiosity :) – Nikita B Aug 24 '16 at 11:51
  • @Clemens, shouldn't I call `Dsipatcher.Run` in order for dispatcher to work? What happens if I call `BeginInvoke` on dispatcher, that is not "running"? – Nikita B Aug 24 '16 at 11:54
  • you mean [this](https://msdn.microsoft.com/en-us/library/system.windows.xps.packaging.xpsdocument%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) `XpsDocument`? I don't see how this is UI element? Create/run `MyCustomControl` on UI thread and only delegate jobs for the `XpsDocument` to a separate thread – slawekwin Aug 24 '16 at 12:02
  • According to my experience, Dispatcher.Run() should not be necessary, but you may try it. The difference between Invoke and BeginInvoke is well documented, e.g. on MSDN – Clemens Aug 24 '16 at 12:46

2 Answers2

0

Here is example of WPF app, which creates Window in new STA Thread. I don't see any problem with it. I printed out some things: Thread name, ThreadId and Counter (changes via INotifyPropertyChanged). Also I change stackPanelCounter's background from timer 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>

Code:

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]));
        }
    }
}
Artavazd Balayan
  • 2,353
  • 1
  • 16
  • 25
  • I do not have a running dispatcher though. Neither do I have or need a new window. – Nikita B Aug 24 '16 at 14:20
  • @NikitaB, pardon my inattention. In your situation, when you're creating just usercontrol in another thread, these links can be helpful: [http://stackoverflow.com/questions/4884802/create-wpf-user-controls-in-other-thread](http://stackoverflow.com/questions/4884802/create-wpf-user-controls-in-other-thread) and [Multithreaded UI: HostVisual](https://blogs.msdn.microsoft.com/dwayneneed/2007/04/26/multithreaded-ui-hostvisual/) – Artavazd Balayan Aug 24 '16 at 15:38
0

I spent some time testing things out, and I think Clemens's comment was accurate. Key points are:

  1. myControl.Dispatcher and Dispatcher.CurrentDispatcher are one and the same, both hold a reference to dispatcher of background thread. No surprises here.
  2. In general controls will not behave correctly without dispatcher running, because Dispatcher.BeginInvoke calls will not be processed. You have two options. Either call Dispatcher.Run() on background thread and create your controls using invokes:

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

    or manually push dispatcher frame every time you want to process dispatcher queue and "refresh" your control. When it comes to building XPS pages, both approaches are viable.

  3. Data bindings do work, even when control is created on background thread. However in some cases they are not applied instantly and you might have to wait for dispatcher to process it's queue.

Community
  • 1
  • 1
Nikita B
  • 3,303
  • 1
  • 23
  • 41