Подтвердить что ты не робот

Возможно ли инициализировать WPF UserControls в разных потоках?

Мы разрабатываем приложение WPF, которое одновременно откроет несколько отчетов (как и в случае с типичным приложением MDI, таким как Excel или Visual Studio). Хотя можно иметь контекст данных для тех отчетов, которые запускаются в нескольких рабочих потоках, мы по-прежнему находим, что если количество открытых отчетов действительно велико, даже рендеринг этих отчетов (в основном UserControl размещен либо в среде MDI, либо только в область сетки в главном представлении) все равно сделает приложение менее отзывчивым.

Итак, моя идея состоит в том, чтобы по крайней мере иметь несколько областей в основном пользовательском интерфейсе, каждый из которых будет управлять своим пользователем в разных потоках пользовательского интерфейса. Опять же, представьте себе типичный вид в визуальной студии, за исключением меню, в нем есть основная область текстового редактора, боковая область, на которой размещаются, например, explorer, и нижняя область, где размещаются, например, список ошибок и вывод. Поэтому я хочу, чтобы эти три области выполнялись в трех потоках пользовательского интерфейса (но, естественно, они размещались в одном MainView, что часть, о которой я не уверен).

Я спрашиваю, потому что знаю, что в разных потоках пользовательского интерфейса может быть несколько (верхнего уровня) окон. Но кто-то сказал, что это не относится к пользовательским элементам управления. Это правда? Если да, то каково типичное решение для моего сценария, т.е. Количество открытых UserControl действительно велико, и многие из этих UserControl в режиме реального времени, поэтому их рендеринг требует огромных ресурсов? Спасибо!

4b9b3361

Ответ 1

Фоновая информация о моделях потоков пользовательского интерфейса

Обычно приложение имеет один "основной" поток пользовательского интерфейса... и может иметь 0 или более потоков фона/рабочего/не-интерфейса, где вы (или среда выполнения/среда .NET) выполняете фоновый рисунок.

(... там еще один специальный поток в WPF, называемый потоком рендеринга, но я пропущу это на данный момент...)

Например, простое приложение WPF может иметь этот список потоков:

enter image description here

И простое приложение WinForms может иметь этот список потоков:

enter image description here

Когда вы создаете элемент, он привязан (имеет сродство) к определенному Dispatcher и потоку и может быть доступен только безопасно из потока, связанного с Dispatcher.

Если вы попытаетесь получить доступ к свойствам или методам объекта из другого потока, вы, как правило, получите исключение, например. в WPF:

enter image description here

В WindowsForms:

enter image description here

Любые изменения в пользовательском интерфейсе должны выполняться в том же потоке, на котором был создан элемент пользовательского интерфейса... поэтому фоновые потоки используют Invoke/BeginInvoke для выполнения этой работы в потоке пользовательского интерфейса.

Демонстрация демонстрации проблем с созданием элементов в теме, отличной от UI

<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);
        }
    }
}

Вторичный поток пользовательского интерфейса aka "Создание окна верхнего уровня в другом потоке"

Возможно создание вторичных UI-потоков, если вы помечаете поток как использующий модель квартиры STA и создаете Dispatcher (например, используйте Dispatcher.Current) и запустите цикл "run" (Dispatcher.Run()), поэтому Dispatcher может обслуживать сообщения для элементов пользовательского интерфейса, созданных в этом потоке.

НО элемент, созданный в одном потоке пользовательского интерфейса, не может быть помещен в логическое/визуальное дерево другого элемента, созданного на другом Пользовательский интерфейс.

Временная технология для микширования элементов, созданных на разных потоках пользовательского интерфейса

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

Ответ 2

Нет, UserControls привязаны к потоку пользовательского интерфейса. Даже если вы смогли инициализировать их в другом месте, у вас возникнут проблемы с добавлением их в основной интерфейс, поскольку они будут принадлежать другому потоку.

Ответ 3

Вы можете разделить рендеринг визуального дерева на разные потоки.

См. эту статью для хорошего объяснения и пример, который отображает вывод видео. http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx

Но делать это действительно реально оправданным, когда фактический рендеринг Visual реализован в другом месте или он технологически очень странен в приложении WPF, например, рендеринг сцены Direct3D как Visual.

Важное замечание здесь, как упоминалось в статье, состоит в том, что если вторичные потоки визуализируют WPF XAML, тогда вы теряете входные события, потому что маршрутизируемые события не могут пересекать границу потока.