Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to initialize WPF UserControls in different threads?

We are developing a WPF application which will open a number of reports at the same time (just like a typical MDI application such as Excel or Visual Studio). Although it is possible to have the data context for those reports run in multiple worker threads, we still find that if the number of opened reports is really big, even the rendering of those reports (basically UserControl hosted either in a MDI environment or just in a grid area in the main view) will still make the application less responsive.

So, my idea is to at least have several areas in the main UI, each of whom will have its user control running in different UI threads. Again, imagine a typical view in visual studio, except for the menus, it has the main area of text editor, a side area which hosts for example solution explorer, and a bottom area which hosts for example error list and output. So I want these three areas to be running in three UI threads (but naturally they are hosted in one MainView, that's the part I am not sure about).

I am asking because I know it is possible to have several (top-level) windows running in different UI threads. But somebody said it doesn't apply to the user controls. Is it true? If so, what is the typical solution to my scenario, i.e., the number of opened UserControl is really big, and many of these UserControl are real-time so rendering of them takes huge amount of resources? Thanks!

like image 886
tete Avatar asked Sep 12 '12 11:09

tete


People also ask

Is WPF multi threaded?

WPF supports a Single Threaded Apartment Model(STA) like the windows forms which introduces following constraints in a WPF application. The thread that creates a WPF UI element owns the elements and other threads can not interact with the UI elements directly,this is known as thread affinity.

Is WPF single threaded?

WPF supports a single-threaded apartment model that has the following rules: One thread runs in the entire application and owns all the WPF objects. WPF elements have thread affinity, in other words other threads can't interact with each other.

How many threads are there in WPF?

WPF applications start their lives with two threads: one for rendering and another for the UI. The rendering thread runs hidden in the background while the UI thread receives input, handles events, paints the screen, and runs application code.

What is dispatcher thread in WPF?

A dispatcher is often used to invoke calls on another thread. An example would be if you have a background thread working, and you need to update the UI thread, you would need a dispatcher to do it.


2 Answers

Background Information on UI Threading Models

Normally an application has one "main" UI thread...and it may have 0 or more background/worker/non-UI threads where you (or the .NET runtime/framework) does background work.

(...there's a another special thread in WPF called the rendering thread but I will skip that for now...)

For example, a simple WPF Application might have this list of threads:

enter image description here

And a simple WinForms Application might have this list of threads:

enter image description here

When you create an element it is tied (has affinity) to a particular Dispatcher & thread and can only be accessed safely from the thread associated with the Dispatcher.

If you try and access properties or methods of an object from a different thread, you will usually get an exception e.g. in WPF:

enter image description here

In WindowsForms:

enter image description here

  • Detecting whether on UI thread in WPF and Winforms

  • http://www.perceler.com/articles1.php?art=crossthreads1

Any modifications to the UI need to be performed on the same thread on which a UI element was created...so background threads use Invoke/BeginInvoke to get that work run on the UI thread.

Demo to Demonstrate Issues with Element Creation on non-UI Thread

<Window x:Class="WpfApplication9.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
    <StackPanel x:Name="mystackpanel">

    </StackPanel>
</Window>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;

namespace WpfApplication9
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Thread m_thread1;
        Thread m_thread2;
        Thread m_thread3;
        Thread m_thread4;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            CreateAndAddElementInDifferentWays();
        }

        void CreateAndAddElementInDifferentWays()
        {
            string text = "created in ui thread, added in ui thread [Main STA]";
            System.Diagnostics.Debug.WriteLine(text);

            CreateAndAddTextChild(text);

            // Do NOT use any Joins with any of these threads, otherwise you will get a
            // deadlock on any "Invoke" call you do.

            // To better observe and focus on the behaviour when creating and
            // adding an element from differently configured threads, I suggest
            // you pick "one" of these and do a recompile/run.

            ParameterizedThreadStart paramthreadstart1 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnThread);
            m_thread1 = new Thread(paramthreadstart1);
            m_thread1.SetApartmentState(ApartmentState.STA);
            m_thread1.Start("[STA]");

            //ParameterizedThreadStart paramthreadstart2 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnUIThread);
            //m_thread2 = new Thread(paramthreadstart2);
            //m_thread2.SetApartmentState(ApartmentState.STA);
            //m_thread2.Start("[STA]");

            //ParameterizedThreadStart paramthreadstart3 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnThread);
            //m_thread3 = new Thread(paramthreadstart3);
            //m_thread3.SetApartmentState(ApartmentState.MTA);
            //m_thread3.Start("[MTA]");

            //ParameterizedThreadStart paramthreadstart4 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnUIThread);
            //m_thread4 = new Thread(paramthreadstart4);
            //m_thread4.SetApartmentState(ApartmentState.MTA);
            //m_thread4.Start("[MTA]");
        }

        //----------------------------------------------------------------------

        void WorkCreatedOnThreadAddedOnThread(object parameter)
        {
            string threadingmodel = parameter as string;

            string text = "created in worker thread, added in background thread, " + threadingmodel;
            System.Diagnostics.Debug.WriteLine(text);

            CreateAndAddTextChild(text);
        }

        void WorkCreatedOnThreadAddedOnUIThread(object parameter)
        {
            string threadingmodel = parameter as string;

            string text = "created in worker thread, added in ui thread via invoke" + threadingmodel;
            System.Diagnostics.Debug.WriteLine(text);

            TextBlock tb = CreateTextBlock(text);
            if (tb != null)
            {
                // You can alternatively use .Invoke if you like!

                DispatcherOperation dispop = Dispatcher.BeginInvoke(new Action(() =>
                {
                    // Get this work done on the main UI thread.

                    AddTextBlock(tb);
                }));

                if (dispop.Status != DispatcherOperationStatus.Completed)
                {
                    dispop.Wait();
                }
            }
        }

        //----------------------------------------------------------------------

        public TextBlock CreateTextBlock(string text)
        {
            System.Diagnostics.Debug.WriteLine("[CreateTextBlock]");

            try
            {
                TextBlock tb = new TextBlock();
                tb.Text = text;
                return tb;
            }
            catch (InvalidOperationException ex)
            {
                // will always exception, using this to highlight issue.
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }

            return null;
        }

        public void AddTextBlock(TextBlock tb)
        {
            System.Diagnostics.Debug.WriteLine("[AddTextBlock]");

            try
            {
                mystackpanel.Children.Add(tb);
            }
            catch (InvalidOperationException ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }
        }

        public void CreateAndAddTextChild(string text)
        {
            TextBlock tb = CreateTextBlock(text);
            if (tb != null)
                AddTextBlock(tb);
        }
    }
}

Secondary UI thread aka "Creating a top-level Window on another thread"

It's possible to create secondary UI-threads, so long as you mark the thread as using the STA apartment model, and create a Dispatcher (e.g. use Dispatcher.Current) and start a "run" loop (Dispatcher.Run()) so the Dispatcher can service messages for the UI elements created on that thread.

  • Dispatcher to Thread relationships in WPF

  • http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/

  • http://www.diranieh.com/NET_WPF/Threading.htm

BUT an element created in one UI thread can't be put into the logical/visual tree of another element which is created on a different UI thread.

Workaround Technique for mixing elements created on different UI threads

There is a limited workaround technique, which may provide you with some ability to compose the rendering of an element created in one UI thread with the visual tree created in a different thread...by using HostVisual. See this example:

  • http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx
like image 193
CSmith Avatar answered Oct 05 '22 06:10

CSmith


No, UserControls are tied to the UI thread. Even if you were able to initialize them elsewhere, you'd have issues adding them to the main UI as they'd belong to a different thread.

like image 21
Joel Lucsy Avatar answered Oct 05 '22 06:10

Joel Lucsy