Ответ 1
Фоновая информация о моделях потоков пользовательского интерфейса
Обычно приложение имеет один "основной" поток пользовательского интерфейса... и может иметь 0 или более потоков фона/рабочего/не-интерфейса, где вы (или среда выполнения/среда .NET) выполняете фоновый рисунок.
(... там еще один специальный поток в WPF, называемый потоком рендеринга, но я пропущу это на данный момент...)
Например, простое приложение WPF может иметь этот список потоков:
И простое приложение WinForms может иметь этот список потоков:
Когда вы создаете элемент, он привязан (имеет сродство) к определенному Dispatcher
и потоку и может быть доступен только безопасно из потока, связанного с Dispatcher
.
Если вы попытаетесь получить доступ к свойствам или методам объекта из другого потока, вы, как правило, получите исключение, например. в WPF:
В WindowsForms:
Любые изменения в пользовательском интерфейсе должны выполняться в том же потоке, на котором был создан элемент пользовательского интерфейса... поэтому фоновые потоки используют 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
. См. Этот пример: