Compartilhar via


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:

Captura de tela de números primos

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.

Ilustração da fila do dispatcher

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.

Captura de tela IU de meteorologia


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. 

MessageBox com um botão "OK"

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:

Diagrama de reentrância de threading

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.

Consulte também

Outros recursos

Single-Threaded o aplicativo de exemplo de cálculo de longa