español - ¿Es posible inicializar los controles de usuario de WPF en diferentes subprocesos?
wpf tutorial (3)
Información de fondo sobre modelos de subprocesos de interfaz de usuario
Normalmente, una aplicación tiene un subproceso de la interfaz de usuario "principal" ... y puede tener 0 o más subprocesos de fondo / de trabajo / no de la interfaz de usuario donde usted (o el tiempo de ejecución .NET / framework) realiza un trabajo de fondo.
(... hay otro subproceso especial en WPF llamado subproceso de representación, pero lo omitiré por ahora ...)
Por ejemplo, una aplicación WPF simple podría tener esta lista de hilos:
Y una aplicación WinForms simple podría tener esta lista de hilos:
Cuando crea un elemento, está vinculado (tiene afinidad) a un Dispatcher
y subproceso en particular y solo se puede acceder de forma segura desde el subproceso asociado con el Dispatcher
.
Si intenta acceder a las propiedades o métodos de un objeto desde un subproceso diferente, generalmente obtendrá una excepción, por ejemplo, en WPF:
En WindowsForms:
Cualquier modificación a la interfaz de usuario debe realizarse en el mismo hilo en el que se creó un elemento de la interfaz de usuario ... para que los subprocesos en segundo plano utilicen Invoke/BeginInvoke
para ejecutar ese trabajo en el subproceso de la interfaz de usuario.
Demostración para demostrar problemas con la creación de elementos en subprocesos que no son 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);
}
}
}
Subproceso de la interfaz de usuario secundaria también conocido como "Crear una ventana de nivel superior en otro hilo"
Es posible crear subprocesos de interfaz de usuario secundarios, siempre que marque el subproceso utilizando el modelo de apartamento STA y cree un Dispatcher
(por ejemplo, utilice Dispatcher.Current
) e inicie un ciclo de "ejecución" ( Dispatcher.Run()
) para que Dispatcher
puede dar servicio a los mensajes de los elementos de la IU creados en ese hilo.
PERO un elemento creado en un subproceso de interfaz de usuario no se puede colocar en el árbol lógico / visual de otro elemento que se crea en un subproceso de interfaz de usuario diferente.
Técnica alternativa para mezclar elementos creados en diferentes hilos de UI
Existe una técnica de solución limitada, que puede proporcionarle cierta capacidad para componer la representación de un elemento creado en un subproceso de la interfaz de usuario con el árbol visual creado en un subproceso diferente ... mediante HostVisual
. Vea este ejemplo:
Estamos desarrollando una aplicación WPF que abrirá una serie de informes al mismo tiempo (al igual que una aplicación MDI típica, como Excel o Visual Studio). Aunque es posible que el contexto de datos para esos informes se ejecute en varios subprocesos de trabajo, todavía encontramos que si el número de informes abiertos es realmente grande, incluso la representación de esos informes (básicamente UserControl alojado en un entorno MDI o simplemente en un área de cuadrícula en la vista principal aún hará que la aplicación sea menos sensible.
Entonces, mi idea es tener al menos varias áreas en la interfaz de usuario principal, cada una de las cuales tendrá su control de usuario ejecutándose en diferentes subprocesos de la interfaz de usuario. Nuevamente, imagine una vista típica en Visual Studio, a excepción de los menús, tiene el área principal del editor de texto, un área lateral que aloja por ejemplo el explorador de soluciones y un área inferior que aloja por ejemplo la lista de errores y la salida. Así que quiero que estas tres áreas se ejecuten en tres subprocesos de la interfaz de usuario (pero, naturalmente, están alojadas en una vista principal, de eso no estoy seguro).
Lo pregunto porque sé que es posible tener varias ventanas (nivel superior) que se ejecutan en diferentes subprocesos de la interfaz de usuario. Pero alguien dijo que no se aplica a los controles del usuario. ¿Es verdad? Si es así, ¿cuál es la solución típica para mi escenario, es decir, la cantidad de UserControl abierto es realmente grande, y muchos de estos UserControl son en tiempo real, por lo que la renderización requiere una gran cantidad de recursos? ¡Gracias!
No, los UserControls están vinculados al hilo de la interfaz de usuario. Incluso si pudieras inicializarlos en otro lugar, tendrías problemas para agregarlos a la interfaz de usuario principal, ya que pertenecían a un hilo diferente.
PUEDES dividir la representación del árbol visual a través de diferentes hilos.
Consulte este artículo para obtener una buena explicación y un ejemplo que muestra la salida de video. http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx
Pero hacer esto solo es realmente justificable cuando la representación real de lo visual se implementa en otro lugar o es tecnológicamente muy extraña en una aplicación WPF, como la representación de una escena Direct3D como un elemento visual.
La nota importante aquí, como se menciona en el artículo, es que si los subprocesos secundarios representan WPF XAML, perderá los eventos de entrada porque los eventos enrutados no pueden cruzar el límite del hilo.