Modelo de Threading
Windows Presentation Foundation (WPF)destina-se para salvar a desenvolvedores de dificuldades de threading. Como resultado, a maioria dos WPF desenvolvedores não terão que escrever uma interface que utiliza mais de um segmento. Como os programas multithread são complexos e difíceis de depurar, eles devem ser evitados quando há soluções single-threaded.
Não importa quão bem projetada, no entanto, não UI estrutura nunca será capaz de fornecer uma solução de thread único para cada tipo de problema. WPF chega perto, mas ainda há situações onde vários segmentos é melhorar user interface (UI) desempenho do aplicativo ou a capacidade de resposta. Depois de discutir o material de plano de fundo, este documento explora algumas 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.
- Overview and the Dispatcher
- Segmentos em ação: As amostras
- Technical Details and Stumbling Points
- Tópicos relacionados
Overview and the Dispatcher
Normalmente, WPF aplicativos começam com dois segmentos: uma para o tratamento de renderização e outro para o gerenciamento de UI. O segmento de processamento efetivamente executa oculto em segundo plano enquanto o UI segmento recebe entrada, trata os eventos, pinta a tela e executa o código do aplicativo. A maioria dos aplicativos usam um único UI segmento, embora em algumas situações é melhor usar vários. Discutiremos isso com um exemplo mais tarde.
O UI itens dentro de um objeto chamado de trabalho a 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.
The trick to building responsive, user-friendly applications is to maximize the Dispatcher throughput by keeping the work items small. This way items never get stale sitting in the Dispatcher queue waiting for processing. Any perceivable delay between input and response can frustrate a user.
Então, como são aplicativos deWPF deve para manipular grandes operações? E se o seu código envolve um cálculo grande ou precisa consultar um banco de dados em algum servidor remoto? Normalmente, a resposta é lidar com a operação grande em um segmento separado, deixando o UI thread livre para tendem a itens na fila deDispatcher . Quando a operação grande estiver concluída, ele pode relatar seu resultado volta para o UI segmento para exibição.
Historicamente, Windows permite que elementos deUI para ser acessado somente pelo thread que os criou. Isso significa que um segmento de plano de fundo 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 o seu conteúdo foram atualizado por um segmento de plano de fundo durante a pintura.
WPFtem um mecanismo interno de exclusão mútua que reforça a tal coordenação. A maioria das classes em WPF derivam de DispatcherObject. Na construção, um DispatcherObject armazena uma referência para o Dispatcher vinculado ao thread em execução no momento. Na verdade, o DispatcherObject associa o thread que o cria. Durante a execução do programa, um DispatcherObject pode chamar seu público VerifyAccess método. VerifyAccess examina a Dispatcher associado ao segmento atual e compará-la para o armazenada durante a construção de referência deDispatcher . Se eles não coincidirem, VerifyAccess lança uma exceção. VerifyAccess se destina a ser chamado no início de cada método pertencentes a um DispatcherObject.
Se apenas um thread pode modificar o UI, como os threads de plano de fundo interagem com o usuário? Um segmento de plano de fundo pode pedir a UI o segmento para executar uma operação em seu nome. Ele faz isso 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. Ambos os métodos agendar um delegado para execução. Invoke é uma chamada síncrona – ou seja, ele não retorna até que o UI segmento realmente termina a execução do delegado. BeginInvoke é assíncrona e retorna imediatamente.
The Dispatcher orders the elements in its queue by priority. There are ten levels that may be specified when adding an element to the Dispatcher queue. These priorities are maintained in the DispatcherPriority enumeration. Detailed information about DispatcherPriority levels can be found in the Windows SDK documentation.
Segmentos em ação: As amostras
A Single-Threaded Application with a Long-Running Calculation
A maioria dos graphical user interfaces (GUIs) gastam uma grande parte do seu tempo ocioso enquanto aguarda a eventos que são gerados em resposta às interações do usuário. Com uma programação cuidadosa esse tempo ocioso pode ser usado construtivamente, sem afetar a capacidade de resposta da UI. O WPF modelo de threading não permite a entrada interromper uma operação acontecendo na UI segmento. Isso significa que deve certificar-se de retornar para o Dispatcher periodicamente para processo pendente de eventos de entrada antes de obterem desatualizados.
Consider the following example:
This simple application counts upwards from three, searching for prime numbers. When the user clicks the Start button, the search begins. When the program finds a prime, it updates the user interface with its discovery. At any point, the user can stop the search.
Although simple enough, the prime number search could go on forever, which presents some difficulties. Se podemos tratados da pesquisa completa dentro do manipulador de eventos click do botão, nós nunca daria a UI o thread a possibilidade de manipular outros eventos. O UI seria incapaz de responder à entrada ou o processo de mensagens. Ele teria nunca redesenhar e nunca responder a cliques de botão.
We could conduct the prime number search in a separate thread, but then we would need to deal with synchronization issues. With a single-threaded approach, we can directly update the label that lists the largest prime found.
If we break up the task of calculation into manageable chunks, we can periodically return to the Dispatcher and process events. We can give WPF an opportunity to repaint and process input.
The best way to split processing time between calculation and event handling is to manage calculation from the Dispatcher. Usando o BeginInvoke método, podemos agendar verificações do número primo na mesma fila que eventos deUI são extraídos. Em nosso exemplo, podemos programar apenas um único número de prime por vez. Após concluir a verificação do número primo, podemos agendar a próxima verificação imediatamente. Essa verificação somente após continua pendente UI eventos foram tratados.
Microsoft Wordrealiza a verificação ortográfica usando esse mecanismo. A verificação ortográfica é feita em segundo plano usando o tempo ocioso da UI segmento. Vamos dar uma olhada no código.
The following example shows the XAML that creates the user interface.
<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>
<Window x:Class="SDKSamples.MainWindow"
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>
The following example shows the code-behind.
Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Threading
Imports System.Threading
Namespace SDKSamples
Partial Public Class MainWindow
Inherits Window
Public Delegate Sub NextPrimeDelegate()
'Current number to check
Private num As Long = 3
Private continueCalculating As Boolean = False
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs)
If continueCalculating Then
continueCalculating = False
startStopButton.Content = "Resume"
Else
continueCalculating = True
startStopButton.Content = "Stop"
startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber))
End If
End Sub
Public Sub CheckNextNumber()
' Reset flag.
NotAPrime = False
For i As Long = 3 To Math.Sqrt(num)
If num Mod i = 0 Then
' Set not a prime flag to true.
NotAPrime = True
Exit For
End If
Next
' If a prime number.
If Not NotAPrime Then
bigPrime.Text = num.ToString()
End If
num += 2
If continueCalculating Then
startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber))
End If
End Sub
Private NotAPrime As Boolean = False
End Class
End Namespace
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 true.
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;
}
}
The following example shows the event handler for the Button.
Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs)
If continueCalculating Then
continueCalculating = False
startStopButton.Content = "Resume"
Else
continueCalculating = True
startStopButton.Content = "Stop"
startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber))
End If
End Sub
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));
}
}
Besides updating the text on the Button, this handler is responsible for scheduling the first prime number check by adding a delegate to the Dispatcher queue. Sometime after this event handler has completed its work, the Dispatcher will select this delegate for execution.
As we mentioned earlier, BeginInvoke is the Dispatcher member used to schedule a delegate for execution. In this case, we choose the SystemIdle priority. O Dispatcher executará esse representante somente quando não existem eventos importantes para o processo. UIcapacidade de resposta é mais importante do que o número de verificação. Podemos também passar um novo delegado que representa a rotina de verificação de número.
Public Sub CheckNextNumber()
' Reset flag.
NotAPrime = False
For i As Long = 3 To Math.Sqrt(num)
If num Mod i = 0 Then
' Set not a prime flag to true.
NotAPrime = True
Exit For
End If
Next
' If a prime number.
If Not NotAPrime Then
bigPrime.Text = num.ToString()
End If
num += 2
If continueCalculating Then
startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber))
End If
End Sub
Private NotAPrime As Boolean = False
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 true.
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;
This method checks if the next odd number is prime. If it is prime, the method directly updates the bigPrime TextBlock to reflect its discovery. We can do this because the calculation is occurring in the same thread that was used to create the component. Podemos tinha escolhido usar um thread separado para o cálculo, teríamos que usam um mecanismo de sincronização mais complicado e executar a atualização no UI segmento. Demonstraremos Avançar essa situação.
Para o código-fonte completo para este exemplo, consulte o Single-Threaded o aplicativo com o exemplo de cálculo de longa
Handling a Blocking Operation with a Background Thread
Handling blocking operations in a graphical application can be difficult. We don’t want to call blocking methods from event handlers because the application will appear to freeze up. Podemos usar um thread separado para tratar essas operações, mas quando terminarmos, temos que sincronizar com o UI segmento porque nós não é possível modificar diretamente o GUI do nosso segmento de trabalho. Podemos usar Invoke ou BeginInvoke para inserir os delegados para o Dispatcher da UI segmento. Eventualmente, esses delegados serão executados com permissão para modificar UI elementos.
In this example, we mimic a remote procedure call that retrieves a weather forecast. We use a separate worker thread to execute this call, and we schedule an update method in the Dispatcher of the UI thread when we’re finished.
Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Media.Imaging
Imports System.Windows.Shapes
Imports System.Windows.Threading
Imports System.Threading
Namespace SDKSamples
Partial Public Class Window1
Inherits Window
' Delegates to be used in placking jobs onto the Dispatcher.
Private Delegate Sub NoArgDelegate()
Private Delegate Sub OneArgDelegate(ByVal arg As String)
' Storyboards for the animations.
Private showClockFaceStoryboard As Storyboard
Private hideClockFaceStoryboard As Storyboard
Private showWeatherImageStoryboard As Storyboard
Private hideWeatherImageStoryboard As Storyboard
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
Private Sub Window_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
' Load the storyboard resources.
showClockFaceStoryboard = CType(Me.Resources("ShowClockFaceStoryboard"), Storyboard)
hideClockFaceStoryboard = CType(Me.Resources("HideClockFaceStoryboard"), Storyboard)
showWeatherImageStoryboard = CType(Me.Resources("ShowWeatherImageStoryboard"), Storyboard)
hideWeatherImageStoryboard = CType(Me.Resources("HideWeatherImageStoryboard"), Storyboard)
End Sub
Private Sub ForecastButtonHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
' Change the status image and start the rotation animation.
fetchButton.IsEnabled = False
fetchButton.Content = "Contacting Server"
weatherText.Text = ""
hideWeatherImageStoryboard.Begin(Me)
' Start fetching the weather forecast asynchronously.
Dim fetcher As New NoArgDelegate(AddressOf Me.FetchWeatherFromServer)
fetcher.BeginInvoke(Nothing, Nothing)
End Sub
Private Sub FetchWeatherFromServer()
' Simulate the delay from network access.
Thread.Sleep(4000)
' Tried and true method for weather forecasting - random numbers.
Dim rand As New Random()
Dim weather As String
If rand.Next(2) = 0 Then
weather = "rainy"
Else
weather = "sunny"
End If
' Schedule the update function in the UI thread.
tomorrowsWeather.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, New OneArgDelegate(AddressOf UpdateUserInterface), weather)
End Sub
Private Sub UpdateUserInterface(ByVal weather As String)
'Set the weather image
If weather = "sunny" Then
weatherIndicatorImage.Source = CType(Me.Resources("SunnyImageSource"), ImageSource)
ElseIf weather = "rainy" Then
weatherIndicatorImage.Source = CType(Me.Resources("RainingImageSource"), ImageSource)
End If
'Stop clock animation
showClockFaceStoryboard.Stop(Me)
hideClockFaceStoryboard.Begin(Me)
'Update UI text
fetchButton.IsEnabled = True
fetchButton.Content = "Fetch Forecast"
weatherText.Text = weather
End Sub
Private Sub HideClockFaceStoryboard_Completed(ByVal sender As Object, ByVal args As EventArgs)
showWeatherImageStoryboard.Begin(Me)
End Sub
Private Sub HideWeatherImageStoryboard_Completed(ByVal sender As Object, ByVal args As EventArgs)
showClockFaceStoryboard.Begin(Me, True)
End Sub
End Class
End Namespace
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);
}
}
}
The following are some of the details to be noted.
Creating the Button Handler
Private Sub ForecastButtonHandler(ByVal sender As Object, ByVal e As RoutedEventArgs) ' Change the status image and start the rotation animation. fetchButton.IsEnabled = False fetchButton.Content = "Contacting Server" weatherText.Text = "" hideWeatherImageStoryboard.Begin(Me) ' Start fetching the weather forecast asynchronously. Dim fetcher As New NoArgDelegate(AddressOf Me.FetchWeatherFromServer) fetcher.BeginInvoke(Nothing, Nothing) End Sub
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); }
When the button is clicked, we display the clock drawing and start animating it. We disable the button. We invoke the FetchWeatherFromServer method in a new thread, and then we return, allowing the Dispatcher to process events while we wait to collect the weather forecast.
Fetching the Weather
Private Sub FetchWeatherFromServer() ' Simulate the delay from network access. Thread.Sleep(4000) ' Tried and true method for weather forecasting - random numbers. Dim rand As New Random() Dim weather As String If rand.Next(2) = 0 Then weather = "rainy" Else weather = "sunny" End If ' Schedule the update function in the UI thread. tomorrowsWeather.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, New OneArgDelegate(AddressOf UpdateUserInterface), weather) End Sub
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); }
To keep things simple, we don’t actually have any networking code in this example. Instead, we simulate the delay of network access by putting our new thread to sleep for four seconds. Nesse tempo, o original UI segmento ainda está sendo executado e respondendo a eventos. Para mostrar isso, deixamos uma animação em execução e a minimizar e maximizar botões também continuam a trabalhar.
Quando termina o atraso e aleatoriamente, selecionamos previsão do tempo, é hora de relatório de volta para o UI segmento. Fazemos isso com o agendamento de uma chamada para UpdateUserInterface na UI segmento usando esse thread Dispatcher. Passamos de uma seqüência de caracteres que descreve o clima para esta chamada de método agendado.
Updating the UI
Private Sub UpdateUserInterface(ByVal weather As String) 'Set the weather image If weather = "sunny" Then weatherIndicatorImage.Source = CType(Me.Resources("SunnyImageSource"), ImageSource) ElseIf weather = "rainy" Then weatherIndicatorImage.Source = CType(Me.Resources("RainingImageSource"), ImageSource) End If 'Stop clock animation showClockFaceStoryboard.Stop(Me) hideClockFaceStoryboard.Begin(Me) 'Update UI text fetchButton.IsEnabled = True fetchButton.Content = "Fetch Forecast" weatherText.Text = weather End Sub
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 na UI segmento tem tempo, ele executa a chamada agendada para UpdateUserInterface. Este método pára a animação do relógio e escolhe uma imagem para descrever o clima. Ele exibe essa imagem e restaura a busca"previsão" botão.
Multiple Windows, Multiple Threads
Alguns WPF aplicativos requerem várias janelas de nível superior. É perfeitamente aceitável para um Thread /combinação deDispatcher para gerenciar várias janelas, mas às vezes de vários segmentos fazer um trabalho melhor. Isso é especialmente verdadeiro se houver alguma chance que uma das janelas será monopolizar o thread.
WindowsExplorer funciona desta maneira. Cada nova janela do Explorer pertence o processo original, mas ele é criado sob o controle de um segmento independente.
Usando um WPF controle deFrame , podemos exibir páginas da Web. Podemos criar facilmente uma simples Internet Explorer substituto. Começamos com um recurso importante: a capacidade de abrir uma nova janela do explorer. Quando o usuário clica "nova janela" botão, iniciamos uma cópia da nossa janela em um thread separado. Dessa forma, as operações de bloqueio ou de longa em uma das janelas não bloquear todas as outras janelas.
In reality, the Web browser model has its own complicated threading model. We’ve chosen it because it should be familiar to most readers.
The following example shows the code.
<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>
Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Threading
Imports System.Threading
Namespace SDKSamples
Partial Public Class Window1
Inherits Window
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
Private Sub OnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
placeHolder.Source = New Uri("https://www.msn.com")
End Sub
Private Sub Browse(ByVal sender As Object, ByVal e As RoutedEventArgs)
placeHolder.Source = New Uri(newLocation.Text)
End Sub
Private Sub NewWindowHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim newWindowThread As New Thread(New ThreadStart(AddressOf ThreadStartingPoint))
newWindowThread.SetApartmentState(ApartmentState.STA)
newWindowThread.IsBackground = True
newWindowThread.Start()
End Sub
Private Sub ThreadStartingPoint()
Dim tempWindow As New Window1()
tempWindow.Show()
System.Windows.Threading.Dispatcher.Run()
End Sub
End Class
End Namespace
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();
}
}
}
The following threading segments of this code are the most interesting to us in this context:
Private Sub NewWindowHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim newWindowThread As New Thread(New ThreadStart(AddressOf ThreadStartingPoint))
newWindowThread.SetApartmentState(ApartmentState.STA)
newWindowThread.IsBackground = True
newWindowThread.Start()
End Sub
private void NewWindowHandler(object sender, RoutedEventArgs e)
{
Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start();
}
Esse método é chamado quando a "nova janela" botão é clicado. It creates a new thread and starts it asynchronously.
Private Sub ThreadStartingPoint()
Dim tempWindow As New Window1()
tempWindow.Show()
System.Windows.Threading.Dispatcher.Run()
End Sub
private void ThreadStartingPoint()
{
Window1 tempWindow = new Window1();
tempWindow.Show();
System.Windows.Threading.Dispatcher.Run();
}
This method is the starting point for the new thread. Criamos uma nova janela sob o controle deste segmento. WPFcria automaticamente uma nova Dispatcher para gerenciar o novo thread. Tudo o que precisamos fazer para tornar a janela funcional é iniciar o Dispatcher.
Technical Details and Stumbling Points
Writing Components Using Threading
O Microsoft .NET Framework Guia do desenvolvedor descreve um padrão de como um componente pode expor o comportamento assíncrono para seus clientes (consulte Event-based Asynchronous Pattern Overview). Por exemplo, suponha que desejássemos pacote o FetchWeatherFromServer o método em um componente reutilizável, não gráficos. Seguindo o padrão Microsoft .NET Framework padrão, isso seria parecido com o seguinte.
Public Class WeatherComponent
Inherits Component
'gets weather: Synchronous
Public Function GetWeather() As String
Dim weather As String = ""
'predict the weather
Return weather
End Function
'get weather: Asynchronous
Public Sub GetWeatherAsync()
'get the weather
End Sub
Public Event GetWeatherCompleted As GetWeatherCompletedEventHandler
End Class
Public Class GetWeatherCompletedEventArgs
Inherits AsyncCompletedEventArgs
Public Sub New(ByVal [error] As Exception, ByVal canceled As Boolean, ByVal userState As Object, ByVal weather As String)
MyBase.New([error], canceled, userState)
_weather = weather
End Sub
Public ReadOnly Property Weather() As String
Get
Return _weather
End Get
End Property
Private _weather As String
End Class
Public Delegate Sub GetWeatherCompletedEventHandler(ByVal sender As Object, ByVal e As GetWeatherCompletedEventArgs)
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 would use one of the techniques described earlier, such as creating a background thread, to do the work asynchronously, not blocking the calling thread.
One of the most important parts of this pattern is calling the MethodNameCompleted method on the same thread that called the MethodNameAsync method to begin with. You could do this using WPF fairly easily, by storing CurrentDispatcher—but then the nongraphical component could only be used in WPF applications, not in Windows Forms or ASP.NET programs.
The DispatcherSynchronizationContext class addresses this need—think of it as a simplified version of Dispatcher that works with other UI frameworks as well.
Public Class WeatherComponent2
Inherits Component
Public Function GetWeather() As String
Return fetchWeatherFromServer()
End Function
Private requestingContext As DispatcherSynchronizationContext = Nothing
Public Sub GetWeatherAsync()
If requestingContext IsNot Nothing Then
Throw New InvalidOperationException("This component can only handle 1 async request at a time")
End If
requestingContext = CType(DispatcherSynchronizationContext.Current, DispatcherSynchronizationContext)
Dim fetcher As New NoArgDelegate(AddressOf Me.fetchWeatherFromServer)
' Launch thread
fetcher.BeginInvoke(Nothing, Nothing)
End Sub
Private Sub [RaiseEvent](ByVal e As GetWeatherCompletedEventArgs)
RaiseEvent GetWeatherCompleted(Me, e)
End Sub
Private Function fetchWeatherFromServer() As String
' do stuff
Dim weather As String = ""
Dim e As New GetWeatherCompletedEventArgs(Nothing, False, Nothing, weather)
Dim callback As New SendOrPostCallback(AddressOf DoEvent)
requestingContext.Post(callback, e)
requestingContext = Nothing
Return e.Weather
End Function
Private Sub DoEvent(ByVal e As Object)
'do stuff
End Sub
Public Event GetWeatherCompleted As GetWeatherCompletedEventHandler
Public Delegate Function NoArgDelegate() As String
End Class
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();
}
Nested Pumping
Às vezes não é viável para bloquear completamente o UI segmento. Vamos considerar o Show método de MessageBox classe. Show não retorna até que o usuário clica no botão OK. No entanto, ele cria uma janela que deve ter um loop de mensagem para ser interativo. Enquanto estamos esperando o usuário clicar em OK, a janela do aplicativo original não responde à entrada do usuário. No entanto, ele, continuar a processar mensagens de pintura. A janela original redesenha próprio quando coberto e revelada.
Alguns segmento deve ser encarregado de janela da caixa de mensagem. WPFfoi possível criar um novo thread apenas para a janela da caixa de mensagem, mas esse thread não conseguiria 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 atual 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 chamada.
In this case, PushFrame maintains the program context at the call to MessageBox.Show, and it starts a new message loop to repaint the background window and handle input to the message box window. When the user clicks OK and clears the pop-up window, the nested loop exits and control resumes after the call to Show.
Stale Routed Events
The routed event system in WPF notifies entire trees when events are raised.
<Canvas MouseLeftButtonDown="handler1"
Width="100"
Height="100"
>
<Ellipse Width="50"
Height="50"
Fill="Blue"
Canvas.Left="30"
Canvas.Top="50"
MouseLeftButtonDown="handler2"
/>
</Canvas>
When the left mouse button is pressed over the ellipse, handler2 is executed. After handler2 finishes, the event is passed along to the Canvas object, which uses handler1 to process it. This happens only if handler2 does not explicitly mark the event object as handled.
É possível que handler2 levará muito tempo processando eventos. handler2pode usar PushFrame para começar um loop de mensagem aninhados que não retorna para horas. If handler2 does not mark the event as handled when this message loop is complete, the event is passed up the tree even though it is very old.
Reentrancy and Locking
O mecanismo de bloqueio da common language runtime (CLR) não se comportam exatamente como se pode imaginar um; esperado um thread para encerrar a operação completamente quando solicitando um bloqueio. Na realidade, o segmento continua a receber e processar mensagens de alta prioridade. Isso ajuda a evitar deadlocks e tornar as interfaces minimamente responsivo, mas introduz a possibilidade de bugs sutis. A grande maioria das vezes você não precisa saber 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 a segurança do thread em mente porque os desenvolvedores trabalham sob a suposição de que uma UI nunca é acessada por mais de um segmento. Neste caso, o que o único thread pode fazer alterações 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 é que a coisa certa, mas há vezes em WPF onde tal reentrância inesperada pode realmente causar problemas. Assim, em determinados momentos importantes, WPF chamadas DisableProcessing, que altera a instrução de bloqueio para esse segmento usar o WPF lock - free reentrância, em vez dos controles usuais CLR lock.
Então, por que fez a CLR equipe escolher esse comportamento? Ela tinha a ver com os objetos COM STA e o thread de finalização. Quando um objeto é coletado como lixo, sua Finalize método não é executado no thread finalizador dedicado, o UI segmento. E aí está o problema, porque um STA COM objeto que foi criado na UI segmento somente pode ser descartado na UI segmento. O CLR oferece o equivalente a uma BeginInvoke (neste caso, usando do Win32 SendMessage). Mas se o UI segmento está ocupado, o thread do finalizador está desativado e o objeto COM STA não pode ser descartado, o que cria um vazamento de memória grave. Assim, a CLR equipe fez a chamada difícil fazer bloqueios funcionam da maneira que eles instrução do
A tarefa de WPF é evitar reentrância inesperada sem a reintrodução de vazamento de memória, o motivo pelo qual nós não bloquear reentrância em todos os lugares.