Is it possible to initialize WPF UserControls in different threads?

后端 未结 3 2024
野趣味
野趣味 2020-12-02 19:16

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

3条回答
  •  北海茫月
    2020-12-02 19:59

    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

    
        
    
        
    
    
    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
    {
        /// 
        /// Interaction logic for MainWindow.xaml
        /// 
        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

提交回复
热议问题