Modelo de Threading
Windows Presentation Foundation (WPF) foi projetado para salvar sistema autônomo desenvolvedores de dificuldades de threading. sistema autônomo resultado, a maioria dos WPF sistema autônomo desenvolvedores não terá de escrever uma interface que utiliza mais de um segmento. sistema autônomo sistema autônomo programas multithread são complexos e difíceis de depurar, eles devem ser evitados quando existem soluções single-threaded.
Não importa bem projetada, no entanto, nenhum UI estrutura será capaz de fornecer uma solução de thread único para cada tipo de problema. WPF chega perto, mas ainda há situações em que vários threads melhoram interface do usuário (UI) desempenho de capacidade de resposta ou aplicativo. Depois de discutir o material de plano de fundo, este documento explora alguns dessas situações e, em seguida, termina com uma discussão de alguns detalhes de nível inferior.
Este tópico contém as seguintes seções.
- Visão geral e o Dispatcher
- Threads em ação: Os exemplos
- Detalhes técnicos e pontos de tropeço
- Tópicos relacionados
Visão geral e o Dispatcher
Normalmente, WPF iniciar aplicativos com dois threads: uma para tratamento de renderização e outro para gerenciar o UI. O thread de renderização efetivamente executa oculto em segundo plano enquanto o UI thread recebe entrada, trata os eventos, pinta a tela e executa o código do aplicativo. A maioria dos aplicativos usa um único UI segmento, embora em algumas situações seja melhor usar vários. Abordaremos isso com um exemplo mais tarde.
The UI itens dentro de um objeto chamado de trabalho filas de thread um Dispatcher. O Dispatcher Seleciona os itens de trabalho em uma base de prioridade e é executado a cada uma delas até a conclusão. Cada UI segmento deve ter pelo menos um Dispatchere cada Dispatcher pode executar os itens de trabalho em exatamente um thread.
O truque para a criação de aplicativos com bom tempo de resposta e amigáveis é maximizar a taxa de transferência do Dispatcher mantendo os itens de trabalho pequenos. Dessa forma, itens nunca ficam velhos sentados na fila do Dispatcher aguardando processamento. Qualquer atraso perceptível entre a entrada e a resposta pode frustrar um usuário.
Então, como são WPF aplicativos devem manipular grandes operações? E se seu código envolve um cálculo grande ou que precise consultar um banco de dados em algum servidor remoto? Normalmente, a resposta for lidar com a operação grande em um segmento separado, deixando o UI thread disponível tendem a itens na Dispatcher fila. Quando a operação grande for concluída, ele pode relatar seu resultado para o UI segmento para exibição.
Historicamente, Windows permite UI elementos de ser acessado somente pelo thread que criou. Isso significa que um plano de fundo thread encarregada de alguma tarefa de execução demorada não é possível atualizar uma caixa de texto quando ele for concluído. Windows faz isso para garantir a integridade da UI componentes. Uma caixa de listagem pode parecer estranha se seu Sumário foram atualizado por um plano de fundo thread durante a pintura.
WPF tem um mecanismo de exclusão mútua interno impõe dessa coordenação. A maioria das classes no WPF derivar de DispatcherObject. Em construção, um DispatcherObject armazena uma referência para o Dispatcher vinculado ao thread em execução no momento. Na verdade, o DispatcherObject associados com o segmento que o cria. Durante a execução do programa, um DispatcherObject pode chamar seu público VerifyAccess método. VerifyAccess examina o Dispatcher associado ao segmento corrente e compará-à Dispatcher referência armazenada durante a construção. Se não houver correspondência, VerifyAccess lança uma exceção. VerifyAccess se destina a ser chamado no início de cada método que pertencem a um DispatcherObject.
Se apenas um thread pode modificar o UI, como fazer plano de fundo threads interagem com o usuário? A plano de fundo thread pode pedir a UI segmento para executar uma operação em seu nome. Isso é feito registrando um item de trabalho com o Dispatcher da UI segmento. O Dispatcher classe fornece dois métodos para registrar itens de trabalho: Invoke e BeginInvoke. Os dois métodos agendar um delegado para execução. Invoke é uma telefonar assíncrono – ou seja, não retorna até que o UI segmento termina, na verdade, executando o delegado. BeginInvoke é assíncrono e retornará imediatamente.
O Dispatcher ordena por prioridade os elementos em sua fila. Existem dez níveis que podem ser especificados ao se adicionar um elemento a fila do Dispatcher. Essas prioridades são mantidas na enumeração DispatcherPriority. Informações detalhadas sobre níveis de DispatcherPriority podem ser encontradas na documentação do Windows SDK.
Threads em ação: Os exemplos
Um aplicativo de thread única com um cálculo demorado
A maioria dos interfaces gráficas do usuário (GUIs) Dedique uma grande parte do seu time ocioso enquanto aguarda a eventos gerados em resposta às interações do usuário. Com programação cuidadosa a time de inatividade pode ser usado crítica, sem afetar a capacidade de resposta das UI. O WPF modelo de Threading não permite a entrada interromper uma operação acontecendo na UI segmento. Isso significa que deve ser-se de retornar para o Dispatcher periodicamente para processo pendentes eventos de entrada antes que eles obtêm obsoletos.
Considere o exemplo a seguir:
Este aplicativo simples conta de três para cima, procurando por números primos. Quando o usuário clica no botão Start, a pesquisa começa. Quando o programa encontrar um primo, ele atualiza a interface do usuário com sua descoberta. Em qualquer ponto, o usuário pode interromper a pesquisa.
Embora simples o suficiente, a pesquisa por números primos poderia continuar para sempre, o que apresenta algumas dificuldades. Se nós tratados toda Pesquisar dentro de um manipulador de eventos de clicar do botão, nós nunca daria a UI thread a oportunidade de se lidar com outros eventos. O UI não conseguirá responder a processo de entrada ou mensagens. Ele poderia nunca redesenhar e nunca responder a cliques de botão.
Nós poderíamos realizar a pesquisa por números primos em uma thread separada, mas então seria preciso lidar com problemas de sincronização. Com uma abordagem de thread única, podemos atualizar diretamente o rótulo que lista o maior primo encontrado.
Se dividirmos a tarefa de cálculo em partes gerenciáveis, podemos periodicamente retornar ao Dispatcher e processar eventos. Nós podemos dar uma oportunidade ao WPF para redesenhar e processar entradas.
A melhor maneira para dividir o tempo de processamento entre cálculo e tratamento de eventos é gerenciar o cálculo a partir do Dispatcher. Usando o BeginInvoke método, podemos marcar número primo verificações na mesma fila que UI eventos são extraídos. Em nosso exemplo, nós programar apenas um único número primo por vez. Após a conclusão da seleção número primo, nós agendar a verificação do próxima imediatamente. Essa verificação somente após continua pendente UI eventos foram tratados.
Microsoft Word realiza a verificação ortográfica usando esse mecanismo. A verificação ortográfica é feita em segundo plano usando o time ocioso das UI segmento. Vamos examinar o código.
O exemplo a seguir mostra o XAML que cria a interface do usuário.
<Window x:Class="SDKSamples.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="Prime Numbers" Width="260" Height="75"
>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
<Button Content="Start"
Click="StartOrStop"
Name="startStopButton"
Margin="5,0,5,0"
/>
<TextBlock Margin="10,5,0,0">Biggest Prime Found:</TextBlock>
<TextBlock Name="bigPrime" Margin="4,5,0,0">3</TextBlock>
</StackPanel>
</Window>
O exemplo a seguir mostra o código para isso:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Threading;
namespace SDKSamples
{
public partial class Window1 : Window
{
public delegate void NextPrimeDelegate();
//Current number to check
private long num = 3;
private bool continueCalculating = false;
public Window1() : base()
{
InitializeComponent();
}
private void StartOrStop(object sender, EventArgs e)
{
if (continueCalculating)
{
continueCalculating = false;
startStopButton.Content = "Resume";
}
else
{
continueCalculating = true;
startStopButton.Content = "Stop";
startStopButton.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new NextPrimeDelegate(CheckNextNumber));
}
}
public void CheckNextNumber()
{
// Reset flag.
NotAPrime = false;
for (long i = 3; i <= Math.Sqrt(num); i++)
{
if (num % i == 0)
{
// Set not a prime flag to ture.
NotAPrime = true;
break;
}
}
// If a prime number.
if (!NotAPrime)
{
bigPrime.Text = num.ToString();
}
num += 2;
if (continueCalculating)
{
startStopButton.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.SystemIdle,
new NextPrimeDelegate(this.CheckNextNumber));
}
}
private bool NotAPrime = false;
}
}
O exemplo a segui mostra o manipulador de eventos para Button.
private void StartOrStop(object sender, EventArgs e)
{
if (continueCalculating)
{
continueCalculating = false;
startStopButton.Content = "Resume";
}
else
{
continueCalculating = true;
startStopButton.Content = "Stop";
startStopButton.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new NextPrimeDelegate(CheckNextNumber));
}
}
Além de atualizar o texto no Button, esse manipulador é responsável por agendar a primeira verificação de número primo, adicionando um delegado a fila do Dispatcher. Algum tempo após esse manipulador de eventos concluir seu trabalho, o Dispatcher selecionará esse delegado para execução.
Como mencionamos anteriormente, BeginInvoke é o membro de Dispatcher usado para agendar um delegado para execução. Nesse caso, nós escolhemos a prioridade SystemIdle. The Dispatcher será executada esse delegado somente quando não há nenhum evento importante para processar. UI capacidade de resposta é mais importante do que a verificação de número. Nós também passar um novo delegado que representa a rotina de verificação de número.
public void CheckNextNumber()
{
// Reset flag.
NotAPrime = false;
for (long i = 3; i <= Math.Sqrt(num); i++)
{
if (num % i == 0)
{
// Set not a prime flag to ture.
NotAPrime = true;
break;
}
}
// If a prime number.
if (!NotAPrime)
{
bigPrime.Text = num.ToString();
}
num += 2;
if (continueCalculating)
{
startStopButton.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.SystemIdle,
new NextPrimeDelegate(this.CheckNextNumber));
}
}
private bool NotAPrime = false;
Esse método verifica se o próximo número ímpar é primo. Se for primo, o método atualiza diretamente o bigPrime TextBlock para refletir sua descoberta. Podemos fazer isso porque o cálculo está ocorrendo na mesma thread que foi usada para criar o componente. Nós tinha escolhido usar um thread separado para o cálculo, teríamos usam um mecanismo de sincronização mais complicado e executar a atualização no UI segmento. Demonstraremos essa situação a seguir.
Para o código fonte completo deste exemplo, consulte Aplicativo de Simples-threaded com exemplo cálculo longos em Executando.
Tratando uma operação bloqueante com uma thread em segundo plano
O tratamento de operações bloqueantes em um aplicativo gráfico pode ser difícil. Não devemos chamar métodos bloqueantes a partir de manipuladores de eventos pois o aplicativo parecerá congelado. Podemos usar um thread separado para tratar essas operações, mas quando podemos terminar, precisamos que sincronizar com o UI thread porque nós não é possível modificar diretamente o GUI de nosso thread de trabalho. Podemos usar Invoke ou BeginInvoke Para inserir delegados para o Dispatcher da UI segmento. Eventualmente, esses delegados serão executados com permissão para modificar UI elementos.
Nesse exemplo, nós imitamos uma chamada de procedimento remoto que recupera uma previsão do tempo. Usamos uma thread de trabalho separada para executar essa chamada e agendamos um método de atualização no Dispatcher da thread de UI quando terminamos.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Threading;
namespace SDKSamples
{
public partial class Window1 : Window
{
// Delegates to be used in placking jobs onto the Dispatcher.
private delegate void NoArgDelegate();
private delegate void OneArgDelegate(String arg);
// Storyboards for the animations.
private Storyboard showClockFaceStoryboard;
private Storyboard hideClockFaceStoryboard;
private Storyboard showWeatherImageStoryboard;
private Storyboard hideWeatherImageStoryboard;
public Window1(): base()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Load the storyboard resources.
showClockFaceStoryboard =
(Storyboard)this.Resources["ShowClockFaceStoryboard"];
hideClockFaceStoryboard =
(Storyboard)this.Resources["HideClockFaceStoryboard"];
showWeatherImageStoryboard =
(Storyboard)this.Resources["ShowWeatherImageStoryboard"];
hideWeatherImageStoryboard =
(Storyboard)this.Resources["HideWeatherImageStoryboard"];
}
private void ForecastButtonHandler(object sender, RoutedEventArgs e)
{
// Change the status image and start the rotation animation.
fetchButton.IsEnabled = false;
fetchButton.Content = "Contacting Server";
weatherText.Text = "";
hideWeatherImageStoryboard.Begin(this);
// Start fetching the weather forecast asynchronously.
NoArgDelegate fetcher = new NoArgDelegate(
this.FetchWeatherFromServer);
fetcher.BeginInvoke(null, null);
}
private void FetchWeatherFromServer()
{
// Simulate the delay from network access.
Thread.Sleep(4000);
// Tried and true method for weather forecasting - random numbers.
Random rand = new Random();
String weather;
if (rand.Next(2) == 0)
{
weather = "rainy";
}
else
{
weather = "sunny";
}
// Schedule the update function in the UI thread.
tomorrowsWeather.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal,
new OneArgDelegate(UpdateUserInterface),
weather);
}
private void UpdateUserInterface(String weather)
{
//Set the weather image
if (weather == "sunny")
{
weatherIndicatorImage.Source = (ImageSource)this.Resources[
"SunnyImageSource"];
}
else if (weather == "rainy")
{
weatherIndicatorImage.Source = (ImageSource)this.Resources[
"RainingImageSource"];
}
//Stop clock animation
showClockFaceStoryboard.Stop(this);
hideClockFaceStoryboard.Begin(this);
//Update UI text
fetchButton.IsEnabled = true;
fetchButton.Content = "Fetch Forecast";
weatherText.Text = weather;
}
private void HideClockFaceStoryboard_Completed(object sender,
EventArgs args)
{
showWeatherImageStoryboard.Begin(this);
}
private void HideWeatherImageStoryboard_Completed(object sender,
EventArgs args)
{
showClockFaceStoryboard.Begin(this, true);
}
}
}
Estes são alguns dos detalhes a serem observados.
Criando o manipulador de botão
private void ForecastButtonHandler(object sender, RoutedEventArgs e) { // Change the status image and start the rotation animation. fetchButton.IsEnabled = false; fetchButton.Content = "Contacting Server"; weatherText.Text = ""; hideWeatherImageStoryboard.Begin(this); // Start fetching the weather forecast asynchronously. NoArgDelegate fetcher = new NoArgDelegate( this.FetchWeatherFromServer); fetcher.BeginInvoke(null, null); }
Quando o botão é clicado, exibimos o desenho do relógio e iniciamos sua animação. Desativamos o botão. Chamamos o método FetchWeatherFromServer em uma nova thread e, em seguida, nós retornar, permitindo que o Dispatcher processe eventos enquanto aguardamos pela coleta da previsão do tempo.
Buscando o tempo
private void FetchWeatherFromServer() { // Simulate the delay from network access. Thread.Sleep(4000); // Tried and true method for weather forecasting - random numbers. Random rand = new Random(); String weather; if (rand.Next(2) == 0) { weather = "rainy"; } else { weather = "sunny"; } // Schedule the update function in the UI thread. tomorrowsWeather.Dispatcher.BeginInvoke( System.Windows.Threading.DispatcherPriority.Normal, new OneArgDelegate(UpdateUserInterface), weather); }
Para manter as coisas simples, não apresentamos qualquer código de rede nesse exemplo. Em vez disso, nós simulamos o atraso do acesso à rede colocando nossa nova thread em modo de espera por quatro segundos. Nesse time, o original UI thread ainda está sendo executada e responder a eventos. Para mostrar isso, que deixamos uma animação em execução e a minimizar e maximizar os botões também continuam a funcionar.
Atualizando a UI
private void UpdateUserInterface(String weather) { //Set the weather image if (weather == "sunny") { weatherIndicatorImage.Source = (ImageSource)this.Resources[ "SunnyImageSource"]; } else if (weather == "rainy") { weatherIndicatorImage.Source = (ImageSource)this.Resources[ "RainingImageSource"]; } //Stop clock animation showClockFaceStoryboard.Stop(this); hideClockFaceStoryboard.Begin(this); //Update UI text fetchButton.IsEnabled = true; fetchButton.Content = "Fetch Forecast"; weatherText.Text = weather; }
Quando o Dispatcher no UI thread tiver time, ele executa a telefonar agendada para UpdateUserInterface. Esse método deixa a animação do relógio e escolhe uma imagem para descrever o clima. Esta imagem exibe e restaura o botão previsão de busca “ ”.
Para o código fonte completo deste exemplo, consulte Simulação de serviço tempo por meio de exemplo do dispatcher.
Várias janelas, várias threads
Alguns WPF os aplicativos requerem várias janelas de nível superior. É perfeitamente aceitável para um thread /Dispatcher combinação para gerenciar várias janelas, mas às vezes, vários threads realizar um trabalho melhor. Isso é especialmente verdadeiro que se houver qualquer chance que uma das janelas será monopolizar o thread.
Windows Explorer funciona dessa maneira. Cada nova janela do Explorer pertence ao processo original, mas é criada sob o controle de um independente thread.
By using a WPF Frame control, we can display Web pages. We can easily create a simple Internet Explorer substitute. Começamos com um recurso importante: a capacidade de em em aberto uma nova janela do explorer. Quando o usuário clica a “ nova janela ” botão, iniciamos uma cópia da nossa janela em uma separada thread. Dessa forma, as operações de bloqueio ou de longa em uma das janelas não bloquear todas as outras janelas.
Na realidade, o modelo de navegador da Web tem seu próprio complicado modelo de threads. Nós o escolhemos pois deve ser familiar a maioria dos leitores.
O exemplo a seguir mostra o código.
<Window x:Class="SDKSamples.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="MultiBrowse"
Height="600"
Width="800"
Loaded="OnLoaded"
>
<StackPanel Name="Stack" Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Content="New Window"
Click="NewWindowHandler" />
<TextBox Name="newLocation"
Width="500" />
<Button Content="GO!"
Click="Browse" />
</StackPanel>
<Frame Name="placeHolder"
Width="800"
Height="550"></Frame>
</StackPanel>
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Threading;
using System.Threading;
namespace SDKSamples
{
public partial class Window1 : Window
{
public Window1() : base()
{
InitializeComponent();
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
placeHolder.Source = new Uri("https://www.msn.com");
}
private void Browse(object sender, RoutedEventArgs e)
{
placeHolder.Source = new Uri(newLocation.Text);
}
private void NewWindowHandler(object sender, RoutedEventArgs e)
{
Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start();
}
private void ThreadStartingPoint()
{
Window1 tempWindow = new Window1();
tempWindow.Show();
System.Windows.Threading.Dispatcher.Run();
}
}
}
Os seguintes segmentos de thread nesse código são os mais interessantes para nós nesse contexto:
private void NewWindowHandler(object sender, RoutedEventArgs e)
{
Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start();
}
Este método é chamado quando o botão de “nova janela” é clicado. Ele cria uma nova thread e a inicia de forma assíncrona.
private void ThreadStartingPoint()
{
Window1 tempWindow = new Window1();
tempWindow.Show();
System.Windows.Threading.Dispatcher.Run();
}
Esse método é o ponto de partida para a nova thread. Criamos uma nova janela sob o controle de neste thread. WPF automaticamente cria um novo Dispatcher para gerenciar o novo thread. Tudo o que precisamos fazer para tornar a janela funcional é iniciar o Dispatcher.
Para o código fonte completo deste exemplo, consulte Multithreading exemplo de navegador da Web.
Detalhes técnicos e pontos de tropeço
Escrever componentes usando threads
The Microsoft .NET Framework Guia do desenvolvedor descreve um padrão para o como um componente pode expor o comportamento assíncrono a seus clientes (consulte Com base em eventos Asynchronous Padrão Overview). Por exemplo, vamos supor que queremos empacotar o FetchWeatherFromServer método em um componente reutilizável, nongraphical. Seguindo o padrão Microsoft .NET Framework padrão, isso teria uma aparência semelhante a seguir.
public class WeatherComponent : Component
{
//gets weather: Synchronous
public string GetWeather()
{
string weather = "";
//predict the weather
return weather;
}
//get weather: Asynchronous
public void GetWeatherAsync()
{
//get the weather
}
public event GetWeatherCompletedEventHandler GetWeatherCompleted;
}
public class GetWeatherCompletedEventArgs : AsyncCompletedEventArgs
{
public GetWeatherCompletedEventArgs(Exception error, bool canceled,
object userState, string weather)
:
base(error, canceled, userState)
{
_weather = weather;
}
public string Weather
{
get { return _weather; }
}
private string _weather;
}
public delegate void GetWeatherCompletedEventHandler(object sender,
GetWeatherCompletedEventArgs e);
GetWeatherAsync usaria uma das técnicas descritas anteriormente, como criar uma thread em segundo plano, para fazer o trabalho de forma assíncrona, não bloqueando a thread de chamada.
Uma das partes mais importantes desse padrão é chamar o método MethodNameCompleted na mesma thread que chamou o método MethodNameAsync para começar. Você pode fazer isso bastante facilmente usando o WPF, armazenando o CurrentDispatcher — mas o componente não gráfico somente poderia ser usado em aplicativos WPF, não em programas Windows Forms ou ASP.NET.
A classe DispatcherSynchronizationContext aborda essa necessidade — pense nela como uma versão simplificada do Dispatcher que funciona com outros frameworks de UI também.
public class WeatherComponent2 : Component
{
public string GetWeather()
{
return fetchWeatherFromServer();
}
private DispatcherSynchronizationContext requestingContext = null;
public void GetWeatherAsync()
{
if (requestingContext != null)
throw new InvalidOperationException("This component can only handle 1 async request at a time");
requestingContext = (DispatcherSynchronizationContext)DispatcherSynchronizationContext.Current;
NoArgDelegate fetcher = new NoArgDelegate(this.fetchWeatherFromServer);
// Launch thread
fetcher.BeginInvoke(null, null);
}
private void RaiseEvent(GetWeatherCompletedEventArgs e)
{
if (GetWeatherCompleted != null)
GetWeatherCompleted(this, e);
}
private string fetchWeatherFromServer()
{
// do stuff
string weather = "";
GetWeatherCompletedEventArgs e =
new GetWeatherCompletedEventArgs(null, false, null, weather);
SendOrPostCallback callback = new SendOrPostCallback(DoEvent);
requestingContext.Post(callback, e);
requestingContext = null;
return e.Weather;
}
private void DoEvent(object e)
{
//do stuff
}
public event GetWeatherCompletedEventHandler GetWeatherCompleted;
public delegate string NoArgDelegate();
}
Bombeamento aninhado
Às vezes não é possível bloquear completamente o UI segmento. Considere Let’s o Show método para o MessageBox classe. Show não retorna até que o usuário clica no botão OK. No entanto, ele faz, criar uma janela que deve ter um loop de mensagem para ser interativos. Enquanto que estiverem aguardando para que o usuário clicar OK, a janela do aplicativo original não responde à entrada do usuário. No entanto, não, continuar a processo pintar mensagens. A janela original redesenha próprio quando coberto e revelado.
Alguns thread deve ser encarregada da janela da caixa de mensagem. WPF pode criar um novo thread apenas para a janela de caixa de mensagem, mas esse thread seria não é possível pintar os elementos desativados na janela original (Lembre-se a discussão anterior sobre a exclusão mútua). Em vez disso, WPF usa uma sistema de processamento de mensagens aninhadas. O Dispatcher classe inclui um método especial chamado PushFrame, que armazena o ponto de execução corrente do aplicativo, em seguida, começa um loop de mensagem nova. Quando o loop de mensagem aninhado termina, a execução reinicia após o original PushFrame Chame.
Nesse caso, PushFrame mantém o contexto do programa na chamada de MessageBox.Show e inicia um novo loop de mensagens para redesenhar a janela do plano de fundo e tratar entrada para a janela da caixa de mensagem. Quando o usuário clica em OK e limpa a janela pop-up, o loop aninhado retorna e o controle continua logo após a chamada a Show.
Eventos roteados obsoletos
O sistema de eventos roteados no WPF notifica árvores inteiras quando os eventos são gerados.
<Canvas MouseLeftButtonDown="handler1"
Width="100"
Height="100"
>
<Ellipse Width="50"
Height="50"
Fill="Blue"
Canvas.Left="30"
Canvas.Top="50"
MouseLeftButtonDown="handler2"
/>
</Canvas>
Quando o botão esquerdo do mouse é pressionado sobre a elipse, handler2 é executado. Quando handler2 termina, o evento é passado para o objeto Canvas, que utiliza handler1 para processá-lo. Isso acontecerá somente se handler2 não marcar explicitamente o objeto de evento como tratado.
É possível que handler2 leva muito time processando este evento. handler2 pode usar PushFrame Para começar um loop de mensagem aninhados que não retorna para horas. Se handler2 não marca o evento como manipulado quando este loop de mensagem for concluído, o evento é passado a árvore mesmo que ele seja muito antigo.
Reentrada e bloqueio
O mecanismo de travamento do common language runtime (CLR) não se comportam exatamente sistema autônomo um pode imaginar; um esperar um thread para encerrar a operação completamente ao solicitar um bloquear. Na verdade, o segmento continua a receber e processar mensagens de alta prioridade. Isso ajuda a evitar deadlocks e fazer interfaces com o mínimo de responder, mas ela introduz a possibilidade de bugs sutis. A grande maioria do time você não precisará sabe nada sobre isso, mas em raras circunstâncias (geralmente envolvendo Win32 mensagens de janela ou componentes COM STA) pode ser que vale a pena conhecer.
A maioria das interfaces não são criadas com acesso thread-safe em mente como os desenvolvedores trabalham sob a suposição de que um UI nunca é acessada por mais de um segmento. Nesse caso, que único thread pode fazer mudanças ambientais em horários inesperados, fazendo com que esses mal efeitos que o DispatcherObject mecanismo de exclusão mútua deve para solucionar. Considere o seguinte pseudocódigo:
Na maioria das vezes isso é a coisa certa, mas há ocasiões em que WPF onde tal reentrância inesperada realmente pode causar problemas. Portanto, em determinados momentos importantes, WPF chamadas DisableProcessing, que altera a instrução de bloquear para esse segmento usar o WPF bloquear disponível reentrância, em vez do normal CLR bloquear.
So why did the CLR team choose this behavior? It had to do with COM STA objects and the finalization thread. When an object is garbage collected, its Finalize method is run on the dedicated finalizer thread, not the UI thread. And therein lies the problem, because a COM STA object that was created on the UI thread can only be disposed on the UI thread. The CLR does the equivalent of a BeginInvoke (in this case using Win32’s SendMessage). But if the UI thread is busy, the finalizer thread is stalled and the COM STA object can’t be disposed, which creates a serious memory leak. So the CLR team made the tough call to make locks work the way they do.
A tarefa do WPF é evitar a reentrância inesperada sem reintroduzir o vazamento de memória, que é o porque de não bloquear reentrada em todos os lugares.
Consulte também
Tarefas
Aplicativo de Simples-threaded com exemplo cálculo longos em Executando
Simulação de serviço tempo por meio de exemplo do dispatcher