web-dev-qa-db-ja.com

異なるスレッドでWPFUserControlsを初期化することは可能ですか?

同時に多数のレポートを開くWPFアプリケーションを開発しています(通常のMDI ExcelやVisualStudioなどのアプリケーションのように)。ただし、データコンテキストを持つことは可能です。複数のワーカースレッドで実行されるこれらのレポートの場合、開いているレポートの数が非常に多い場合は、それらのレポートのレンダリング(基本的にはMDI環境または単にメインビューのグリッド領域)でも、アプリケーションの応答性が低下します。

したがって、私の考えは、メインUIに少なくともいくつかの領域を持たせ、それぞれの領域でユーザーコントロールを異なるUIスレッドで実行することです。ここでも、メニューを除いて、Visual Studioの一般的なビューを想像してください。メイン領域にはテキストエディタがあり、側面領域にはソリューションエクスプローラーなどがあり、下部領域にはエラーリストや出力などがあります。したがって、これら3つの領域を3つのUIスレッドで実行する必要があります(ただし、当然、これらは1つのMainViewでホストされます。これは、私にはよくわかりません)。

異なるUIスレッドで複数の(トップレベルの)ウィンドウを実行できることがわかっているので、質問しています。しかし、誰かがそれはユーザーコントロールには適用されないと言いました。それは本当ですか?もしそうなら、私のシナリオの典型的な解決策は何ですか?つまり、開かれたUserControlの数は本当に多く、これらのUserControlの多くはリアルタイムであるため、それらのレンダリングには膨大な量のリソースが必要ですか?ありがとう!

18
tete

UIスレッドモデルの背景情報

通常、アプリケーションには1つの「メイン」UIスレッドがあります...そして、あなた(または.NETランタイム/フレームワーク)がバックグラウンド作業を行う0個以上のバックグラウンド/ワーカー/非UIスレッドがある場合があります。

(... WPFにはレンダリングスレッドと呼ばれる別の特別なスレッドがありますが、今はスキップします...)

たとえば、単純なWPFアプリケーションには、次のスレッドのリストがあります。

enter image description here

また、単純なWinFormsアプリケーションには、次のスレッドのリストが含まれている場合があります。

enter image description here

要素を作成すると、その要素は特定のDispatcher&スレッドに関連付けられ(親和性があり)、Dispatcherに関連付けられたスレッドからのみ安全にアクセスできます。

別のスレッドからオブジェクトのプロパティまたはメソッドにアクセスしようとすると、通常、例外が発生します。 WPFの場合:

enter image description here

WindowsFormsの場合:

enter image description here

UIへの変更は、UI要素が作成されたのと同じスレッドで実行する必要があります...したがって、バックグラウンドスレッドは_Invoke/BeginInvoke_を使用してその作業をUIスレッドで実行します。

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

セカンダリUIスレッド別名「別のスレッドでのトップレベルウィンドウの作成」

スレッドをSTAアパートメントモデルを使用するものとしてマークし、Dispatcherを作成して(たとえば、_Dispatcher.Current_を使用)、「実行」ループを開始する限り、セカンダリUIスレッドを作成することができます(Dispatcher.Run())なので、Dispatcherはそのスレッドで作成されたUI要素のメッセージを処理できます。

ただし、あるUIスレッドで作成された要素を、別のUIスレッドで作成された別の要素の論理/ビジュアルツリーに配置することはできません。

異なるUIスレッドで作成された要素を混合するための回避策

制限された回避策があり、HostVisualを使用して、あるUIスレッドで作成された要素のレンダリングを別のスレッドで作成されたビジュアルツリーで構成する機能を提供する場合があります。この例を参照してください。

23
Colin Smith

いいえ、UserControlsはUIスレッドに関連付けられています。それらを他の場所で初期化できたとしても、それらは別のスレッドに属しているため、メインUIに追加する際に問題が発生します。

1
Joel Lucsy

ビジュアルツリーのレンダリングを異なるスレッドに分割できます。

ビデオ出力をレンダリングする適切な説明と例については、この記事を参照してください。 http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx

ただし、これを行うことは、ビジュアルの実際のレンダリングが他の場所に実装されている場合、またはDirect3DシーンをビジュアルとしてレンダリングするなどのWPFアプリケーションで技術的に非常に奇妙な場合にのみ本当に正当化されます。

ここで重要な注意点は、記事で説明されているように、セカンダリスレッドがWPF XAMLをレンダリングすると、ルーティングされたイベントがスレッドの境界を越えることができないため、入力イベントが失われることです。

0
Tamir Daniely