Introdução aos Routed Events

Assim como as Dependency Properties, uma outra grande inovação no WPF foram os Routed Events. As dependency properties adicionam às propriedades uma série de recursos adicionais, quando comparadas às propriedades tradicionais do .NET Framework, e o mesmo acontece com os routed events, fornecendo recursos bastantes interessantes, trazendo uma riqueza maior para os eventos, e que o WPF faz uso intenso disso.

Antes de efetivamente falar sobre os routed events, primeiro precisamos conhecer dois conceitos: logical e visual trees. Ambos tem a finalidade de descrever a interface que foi criada de forma declarativa ou imperativa, mas diferem na forma como visualizamos esses elementos. No primeiro caso, logical, cada elemento dentro desta árvore será visualizado de forma “macro”, por exemplo, se tiver um controle do tipo Button, ele será apenas um Button com uma string que representa o seu texto; já o segundo conceito, visual, faz com que ao invés de visualizarmos o controle como um todo, ele “explode” o mesmo, permitindo o acesso a cada objeto que compõe a construção dele, e no caso do Button, teríamos o Button -> ButtonChrome -> ContentPresenter -> TextBlock.

Os conceitos que vimos acima é importante, pois tanto para dependency properties quanto para os routed events, eles utilizam a árvore lógica para a propagação de informações (dependency properties) e de ações (routed events). Como vimos acima, o Button é composto de vários outros sub-elementos, mas quando o usuário efetua o clique no botão, muito provavelmente ele poderá clicar em cima do TextBlock, e sendo assim, como o WPF traduzirá, ou melhor, entregará este evento para o evento Click do botão correspondente? Tudo isso graças ao recurso de propagação de eventos através da árvore visual.

A implementação de um routed event é bem semelhante a forma que criamos uma dependency property, ou seja, o evento é definido através de uma classe chamada RoutedEvent, declarada como estática e marcada como readonly no topo da classe onde ele será exposto. No construtor estático desta mesma classe, utilizamos o método estático RegisterRoutedEvent da classe EventManager, onde especificamos o nome do evento, a estratégia de roteamento, o tipo do event handler e o tipo da classe onde ele está sendo criado. Abaixo temos uma implementação de um evento em um controle:

public partial class MeuControle : UserControl
{
    public static readonly RoutedEvent ChangedStateEvent;

    static MeuControle()
    {
        ChangedStateEvent =
            EventManager.RegisterRoutedEvent(
                “ChangedState”,
                RoutingStrategy.Tunnel, 
                typeof(RoutedEventHandler), 
                typeof(MeuControle));
    }

    public event RoutedEventHandler ChangedState
    {
        add { AddHandler(MeuControle.ChangedStateEvent, value); }
        remove { RemoveHandler(MeuControle.ChangedStateEvent, value); }
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        this.RaiseEvent(new RoutedEventArgs(MeuControle.ChangedStateEvent, this));
    }
}

O primeiro ponto importante a se notar, é a nomenclatura do evento. Por convenção, adotou-se sufixar com a palavra “Event”, mas isso fica coerente quando estamos criando tudo em inglês; se o nome do evento for “EstadoAlterado”, eu considero desnecessário este sufixo. Além disso, temos um evento tradicional do .NET, chamado de ChangedState. Isso serve apenas como um wrapper para o routed event que foi criado acima (lembre-se que as dependency properties também são expostas através de um wrapper), interceptando a adição e remoção do delegate que tratará o evento, recorrendo aos métodos AddHandler e RemoveHandler, respectivamente, que foram herdados da classe UIElement. E, finalmente, chega o momento de disparar o evento, e para isso utilizamos o método RaiseEvent, que dado os argumentos (falaremos mais abaixo sobre eles) correspondente ao delegate do evento, ele irá dispará-lo e os interessados poderão se anexar ao mesmo.

Os routed events nos permite ainda vincular à um evento não necessariamente em cima do próprio controle, mas através de qualquer controle dentro da hierarquia. Por exemplo, se tivermos um formulário WPF, e dentro dele um StackPanel, e dois botões dentro deste painel, o XAML correspondente seria:

<Window …>
    <Grid>
        <StackPanel>
            <Button Name=”btn1″ Click=”btn1_Click”>Botao 1</Button>
            <Button Name=”btn2″ Click=”btn2_Click”>Botao 2</Button>
        </StackPanel>
    </Grid>
</Window>

A pergunta é: onde tratar o evento Click de cada botão? Se eu tratar o evento diretamente no botão (que é o mais comum), isso fará com que eu crie um event handler para cada botão dentro do StackPanel. Então porque não centralizar isso, onde independemente de qual botão for clicado, eu redirecionará sempre para o mesmo event handler? Poderia resolver isso vinculando o evento Click de todos os botões para o mesmo event handler, mas uma forma mais simples de fazer isso, é vinculando o evento ao controle que está acima na hierarquia do XAML, por exemplo:

<Window …>
    <Grid>
        <StackPanel ButtonBase.Click=”AlgumBotaoFoiClicado”>
            <Button Name=”btn1″>Botao 1</Button>
            <Button Name=”btn2″>Botao 2</Button>
        </StackPanel>
    </Grid>
</Window>

Mas e se haverem mais do que um StackPanel dentro do meu formulário? Podemos então elevar o tratamento do evento para nível de formulário, como é mostrado abaixo:

<Window … ButtonBase.Click=”AlgumBotaoFoiClicado”>
    <Grid>
        <StackPanel>
            <Button Name=”btn1″>Botao 1</Button>
            <Button Name=”btn2″>Botao 2</Button>
        </StackPanel>
        <StackPanel>
            <Button Name=”btn3″>Botao 3</Button>
            <Button Name=”btn4″>Botao 4</Button>
        </StackPanel>
    </Grid>
</Window>

O que determina como esses eventos serão disparados/propagados, é a estratégia de roteamento que você define quando cria o evento. Para isso, utilizamos o enumerador RoutingStrategy, que podemos escolher uma entre as três opções abaixo:

  • Tunnel: O evento é primeiramente disparado no controle raiz e depois em cima de todos os elementos até chegar no controle que efetivamente disparou o evento.
  • Bubble: O evento é primeiramente disparado no controle que efetivamente disparou o evento, subindo até a raiz.
  • Direct: O evento somente é disparado em cima do elemento de origem, assim como acontece com os eventos tradicionais do .NET.

Para utilizar o controle que criamos acima e consumir o evento, teremos um código semelhante a este:

<Window …
    xmlns:app=”clr-namespace:WpfApplication1″
    app:MeuControle.ChangedState=”MeuControle_ChangedState”>
    <Grid>
        <app:MeuControle ChangedState=”MeuControle_ChangedState” />
    </Grid>
</Window>

private void MeuControle_ChangedState(object sender, RoutedEventArgs e)
{
    MessageBox.Show(“Sender: ” + sender.ToString());
}

Se o evento for criado com a opção Tunnel, as mensagens serão: “Window1” e “MeuControle”; se definir a opção como Bubble, as mensagens serão “MeuControle” e “Window1” e, finalmente, se definir como Direct, somente aparecerá “MeuControle”.

A assinatura dos event handlers dos routed events seguem, basicamente, o mesmo padrão de eventos tradicionais do .NET. O primeiro parâmetro (sender) é do tipo System.Object, que informa o objeto que disparou o evento. Já o segundo parâmetro (e) é uma classe do tipo RoutedEventArgs, que expõe quatro propriedades que trazem informações contextuais:

  • Source: System.Object que representa o elemento na árvore lógica que originalmente invocou o evento.
  • OriginalSource: System.Object que representa o elemento na árvore visual que originalmente disparou o evento.
  • Handled: Valor boleano que indica se o evento deverá prosseguir com a propagação para os elementos subsequentes da árvore. Se definí-lo como False, o próximo não será disparado.
  • RoutedEvent: A instância do routed event que foi disparado, que pode ser usado quando você direciona vários eventos para o mesmo event handler.

Conclusão: Assim como já acontece com as dependency properties, em um primeiro momento, esse tipo de evento torna o código um pouco mais complicado de ler, mas por outro lado, traz uma série de benefícios que você não consegue com os eventos tradicionais do .NET.

Anúncios

Introdução às Dependency Properties

Quando desenvolvemos classes e/ou controles, geralmente expomos as suas características através de propriedades. Na maioria das vezes, essas propriedades encapsulam o acesso à membros privados internos, interceptando a escrita e leitura dos mesmos. Os controles ASP.NET geralmente não possuem esses membros internos, e armazenam o conteúdo diretamente no ViewState ou no ControlState da respectiva página.

As propriedades também são utilizadas por controles Windows Forms, seguindo a mesma estratégia das propriedades tradicionais que as linguagens disponibilizam. Mas aplicações Windows geralmente tem algumas características próprias, como validação e, principalmente, databinding, que é a possibilidade de vincular um objeto à algum controle de interface (UI). Há também algumas tarefas que são comuns em aplicações deste tipo, como por exemplo, a notificação da alteração de um valor, que fará eventuais mudanças nos demais controles.

Para tornar esse trabalho mais suave, a Microsoft introduziu, desde a primeira versão do WPF, as Dependency Properties. Esse tipo especial de propriedade é semelhante as propriedades das linguagens, mas com muito mais poder. A inteligência envolvida neste novo tipo de propriedade, permite trabalharmos com o código puramente declarativo (XAML), sem precisar de qualquer código procedural para efetuar alguma mudança na aparência quando algo acontecer.

Além disso, temos uma série de outros benefícios ao utilizá-las. O primeiro deles é a capacidade de sermos notificados quando o valor dessa propriedade for alterado, e o próprio sistema de Triggers do WPF faz uso desta funcionalidade, onde você consegue associar um valor à uma propriedade, e quando ela receber este valor, você poderá tomar alguma decisão, como alterar a cor de outro controle, disparo de outros eventos, etc. Há também outros pontos positivos que vamos analisar mais tarde, ainda neste artigo.

A sua utilização é relativamente simples, e não dispensa completamente o uso das propriedades tradicionais fornecidas pelas linguagens. As classes (ou controles) que querem fazer uso destas propriedades, deverão herdar direta ou indiretamente da classe DependencyObject, pois essa classe permitirá ao objeto participar do serviço de propriedades que o WPF disponibiliza. Além disso, para que uma propriedade seja registrada e, consequentemente, poder fazer uso de toda essa infraestrutura, cada propriedade exposta deverá manter um campo dentro da classe onde ela será declarada, definindo o tipo deste campo como sendo DependencyProperty. Abaixo temos a estrutura de uma dependency property:

public static readonly DependencyProperty TitleProperty =
    DependencyProperty.Register(
        “Title”, 
        typeof(string), 
        typeof(MeuControle));

public string Title
{
    get
    {
        return this.GetValue(TitleProperty).ToString();
    }
    set
    {
        this.SetValue(TitleProperty, value);
    }
}

O primeiro ponto que vemos é que a classe DependencyProperty não é instanciada diretamente. Para registrá-la, você precisa invocar o método estático Register (dessa mesma classe), que em sua versão mais básica, receberá o nome da propriedade, o tipo da propriedade e o tipo onde essa propriedade está sendo criada. Outro ponto bastante curioso é que esse campo é declarado como estático. Mas e se houverem várias instâncias dessa classe, o valor da propriedade será compartilhado? Não. Na verdade, as propriedades são salvas em uma espécie de dicionário, onde a chave deverá ser única dentro do tipo. Aqui é a grande diferença, ou seja, ao invés de termos um campo privado para cada propriedade exposta, que muitas vezes ficavam com o seu valor padrão, as dependency properties resolvem este problema armazenando somente os valores que são modificados pela instância, enquanto as outras compartilham um – mesmo – valor padrão, reutilizando o valor padrão por todas as instâncias.

A seguir temos a propriedade Title, que por sua vez, serve apenas como um wrapper para o objeto que criamos acima, expondo o tipo efetivo da propriedade, que no caso acima é string. Note que em momento nenhum você acessa o objeto TitleProperty diretamente; toda a manipulação é realizada pelos métodos GetValue e SetValue, para ler e escrever, respectivamente. Como vimos, esses métodos estão disponíveis para todas as classes que derivam da classe DependencyObject, que realiza todas as etapas necessárias para determinar se o valor já foi sobrescrito pela instância corrente, e caso tenha sido, este será retornado ao invés de seu valor padrão.

Ainda há a possibilidade de registrar uma dependency property como somente leitura, pois pode haver propriedades que apenas reportam o estado interno da classe, como por exemplo, a propriedade IsMouseOver da classe Control, que retorna um valor boleano indicando se o ponteiro está ou não posicionado em cima daquele controle. Neste caso, o valor é definido internamente pelo WPF.

A classe DependencyProperty fornece para essa finalidade, um método também estático, chamado RegisterReadOnly, que depois de registrado, retornará uma instância da classe DependencyPropertyKey, representando a chave para a propriedade recém registrada.

private static readonly DependencyPropertyKey TitlePropertyKey =
      DependencyProperty.RegisterReadOnly(
          “Title”, 
          typeof(string),
          typeof(MeuControle));
 
public static readonly DependencyProperty TitleProperty =
      TitlePropertyKey.DependencyProperty;

public string Title
{
    get
    {
        return this.GetValue(TitleProperty).ToString();
    }
    private set
    {
        this.SetValue(TitlePropertyKey, value);
    }
}

Neste caso, veja que a chave é declarada de forma privada, para que somente o interior da classe onde ela é declarada tenha acesso. A criação do objeto DependencyProperty ainda é necessário, mas não será através do método Register. A classe DependencyPropertyKey define uma propriedade chamada DependencyProperty, que retorna a propriedade para acessá-la através do método GetValue, seguindo o mesmo esquema que vimos acima. Na propriedade Title, a escrita também está protegida pelo modificador private, e note que a alteração está sendo feita, passando o chave e não a instância da DependencyProperty como fizemos no primeiro exemplo.

Metadados

Como vimos acima, a classe DependencyProperty ainda fornece outras versões (overloads) do método Register. O que veremos a seguir, é aquele que recebe os metadados. Através destes metadados, poderemos configurar uma série de características destas propriedades, como por exemplo: valor padrão, notificação quando o valor for alterado, coerção, etc. A principal classe que podemos utilizar para definição destes metadados, é a FrameworkPropertyMetadata.

Entre as principais funcionalidades que esta classe fornece, temos a possibilidade de informar o valor padrão da propriedade, e enquanto você não definir um novo valor para uma propriedade, será este valor que será retornado. Além disso, podemos definir através de delegates, ações que desejamos executar quando o valor for alterado ou alguma ação para efetuar uma coerção no valor. Abaixo temos o exemplo de como utilizar esses metadados:

public static readonly DependencyProperty TitleProperty =
    DependencyProperty.Register(
        “Title”,
        typeof(string),
        typeof(MeuControle),
        new FrameworkPropertyMetadata(
            “Título Indefinido”,
            (o, e) => MessageBox.Show(e.NewValue.ToString()),
            (d, e) => string.Format(“*** {0} ***”, e)));

O primeiro parâmetro informado no construtor da classe FrameworkPropertyMetadata, é o valor padrão da propriedade. Esse parâmetro é do tipo System.Object, pois a propriedade poderá ser de qualquer tipo. Todas as instâncias desta classe sempre compartilharão este mesmo valor, a menos que uma instância específica o sobrescreva, e a partir daí, esta instância terá o seu próprio valor.

Os dois parâmetros seguintes são delegates, onde o primeiro deles (PropertyChangedCallback) representa o método que será disparado quando o valor desta propriedade for alterado. O argumento “e” é do tipo DependencyPropertyChangedEventArgs, e entre suas propriedades, temos OldValue e NewValue, que são autoexplicativas. Já o terceiro parâmetro (CoerceValueCallback), define um método de coerção, que tem a finalidade de customizar/formatar a entrada da informação de acordo com alguma regra, que acontecerá antes de armazenar o valor definitivamente. O “e” que vemos sendo passado para este método, traz o valor atual; a assinatura deste delegate também retorna um System.Object, que é justamente o valor depois de alterado.

Validação

Outro ponto importante quando falamos sobre propriedades, é a questão de validação. As validações geralmente são avaliadas antes do valor ser efetivamente armazenado. As dependency properties também já trazem a validação nativamente, que podemos configurar e vincular durante a criação de uma propriedade.

Para isso, o método Register ainda fornece em um de seus overloads, um parâmetro do tipo ValidateValueCallback, que também é um delegate e que recebe o valor que está sendo passado para a propriedade e retorna um valor boleano, indicando se o parâmetro está válido ou não. O seu uso está sendo exibido abaixo, e note que antes da propriedade receber o valor, a consistência para verificar se ele está correta será avaliada, e caso retorne False, uma exceção será disparada.

public static readonly DependencyProperty TitleProperty =
    DependencyProperty.Register(
        “Title”,
        typeof(string),
        typeof(MeuControle),
        new FrameworkPropertyMetadata(
            “Sem Título Definido”,
            (o, e) => MessageBox.Show(e.NewValue.ToString()),
            (d, e) => string.Format(“*** {0} ***”, e)),
        p => p != null && p.ToString().Length > 0);

Conclusão: Apesar de no primeiro momento tornar o código um pouco ilegível, percebermos no decorrer deste artigo o poder deste tipo de propriedade. Grande parte das tarefas de manipulação de UI, e que antes ficavam espalhadas pelo código, agora podemos centralizar, tornando a manutenção e reutilização muito mais simples. Isso sem contar que utilizar esse tipo de propriedade habilita grande parte dos recursos exclusivos do WPF, como estilos, databinding, etc.

Threading em WPF

Quando desenvolvemos aplicações Windows, é muito comum em algum ponto da mesma, que algumas tarefas mais complicadas e custosas sejam realizadas, que podem levar um tempo maior até que seja concluída. Independentemente do que ela faça, seja um cálculo, uma consulta em uma base de dados ou uma chamada para um serviço, se você executar esse código de forma síncrona, o usuário deverá esperar até que essa tarefa seja finalizada, para a partir daí, conseguir acessar outras áreas do sistema.

Ao rodar uma aplicação Windows, um processo é criado dentro do sistema operacional. Processo não executa nenhum código; são as threads que fazem isso. Quando o processo é criado, uma thread é criada juntamente com ele, e esta é muitas vezes chamada de main-thread (thread principal). Essa thread nasce e morre com o término do processo, ou seja, enquanto ela estiver executando alguma tarefa, o processo continuará ativo.

As aplicações Windows que conhecemos, como Windows Forms, Console, Windows Services e WPF trabalham nesta mesma linha. Aplicações que possuem gráficos, como é o caso do Windows Forms e do WPF, tem um agravante: a afinidade que os controles tem com a thread principal. Quando a aplicação é iniciada, a thread principal é quem cria os controles (Form, TextBox, Label, TextBlock, etc.), e quando dizemos que há uma afinidade, isso quer dizer que podemos somente manipular esses controles, através da mesma thread que os criaram, e qualquer tentativa de fazer isso através de uma segunda thread, uma exceção do tipo InvalidOperationException será disparada.

Quando essas tarefas são finalizadas, é normal queremos exibir o resultado para o usuário, que na maioria das vezes, implica em alterar a propriedade Text de algum controle, exibir uma MessageBox, etc. Em Windows Forms, todos os controles herdam direta ou indiretamente da classe Control, que fornece um método chamado Invoke, e que dado um delegate, executa o método relacionado na mesma thread do criador. Mais tarde, com o .NET Framework 2.0, surgiu o SynchronizationContext, que facilitou bastante a atualização dos controles a partir de uma thread secundária.

Agora temos o WPF, que traz novas funcionalidades e uma forma um pouco diferente para lidar com esse tipo de problema. Vamos a partir deste artigo, explorar um pouco mais sobre o modelo de threading do WPF. A Microsoft introduziu uma série de novos tipos, espalhados por vários namespaces e que serão utilizados para conseguir atingir o nosso principal objetivo. Para iniciar, o primeiro tipo que temos que conhecer é a classe Dispatcher. Essa classe serve como um gerenciador de tarefas para serem executadas, e está sempre associada com uma determinada thread de UI. Ela mantém uma fila de tarefas que são executadas utilizando a thread qual está relacionada.

A fila que é mantida pela classe Dispatcher é priorizada, que permite especificar uma prioridade antes de enfileirar a tarefa (mais detalhes abaixo). Para alistar uma tarefa nesta fila, você poderá utilizar o método Invoke ou BeginInvoke. A diferença entre eles é clara: o primeiro executa a tarefa de forma síncrona, enquanto a segunda alista a tarefa para ser executada de forma assíncrona. E para sedimentar, ambas sempre executarão na thread ao qual o Dispatcher está vinculado.

Grande parte das classes que compõem o framework do WPF, incluindo os controles, herda direta ou indiretamente da classe abstrata DispatcherObject, que possui uma estrutura simples, ou seja, fornece uma propriedade chamada Dispatcher que retorna a instância de uma classe Dispatcher, e como já era de esperar, fornece a instância do Dispatcher que está vinculado com aquele classe/controle.

A classe DispatcherObject ainda fornece dois métodos importantes: CheckAccess e VerifyAccess. A diferença entre eles é que o primeiro retorna um valor boleano, indicando se a thread que está chamando tem direito de acesso ao Dispatcher correspondente. Já o segundo método, VerifyAccess, dispara uma exceção do tipo InvalidOperationException, caso a thread que está chamando não tiver direito de acesso ao Dispatcher. Como pudemos perceber, esses métodos vão nos auxiliar para determinar se há ou não a necessidade de atualizar o controle através da thread atual, sem que seja necessário utilizar o Dispatcher para chegar até o controle.

Para exemplificar, imagine que temos uma thread que executará algum cálculo complexo, e depois de calculado, deverá exibir o resultado em um TextBox. Como comentado acima, dentro desta thread não podemos alterar qualquer propriedade do TextBox, e para solucionar isso no WPF, vamos recorrer a propriedade Dispatcher do TextBox (“txt”), que foi herdada de DispatcherObject. Ao passar um delegate para o método Invoke, ele será executado (de forma síncrona) na mesma thread que criou o controle.

new Thread(() =>
{
    int result = 2 ^ 4 * 2 + 3 / 3;
    Thread.Sleep(3000); //Simula Processo Complexo

    txt.Dispatcher.Invoke(new Action<int>(r => txt.Text = r.ToString()), result);
}).Start();

Se desejar, podemos trocar o método Invoke por BeginInvoke, e a atualização do controle será feita em background. A vantagem desta técnica é que você pode executar a atualização do controle enquanto faz outras tarefas. É importante que você mantenha tarefas “leves” dentro do Dispatcher, pois tudo o que ele deveria fazer ali é atualizar a UI; colocar tarefas mais complexas, voltará a ter concorrência com os eventos dos controles e, consequentemente, o usuário voltará a ter os travamentos das telas do sistema, que acontecia quando trabalhávamos de forma síncrona.

Quando você opta por utilizar o método BeginInvoke, ele retorna uma instância da classe DispatcherOperation. Basicamente, este objeto representa uma espécie de “ponteiro” para a operação que está sendo executada. Essa classe fornece uma série de membros interessantes, e entre eles temos:

  • Dispatcher: O Dispatcher relacionado.
  • Priority: Uma das opções definidas no enumerador DispatcherPriority, que define a prioridade da operação.
  • Result: Retorna um System.Object com o resultado da tarefa (isso quando ela retornar algum resultado).
  • Status: Uma das opções definidas no enumerador DispatcherOperationStatus, que define o status atual da operação (Pending, Aborted, Completed ou Execution).
  • Abort: Método que aborta a operação que está sendo executada.
  • Wait: Quando invocado, fará um “join” na thread atual, aguardando até o término da operação. Opcionalmente você pode especificar um timeout.
  • Aborted: Evento que é disparado quando a operação é abortada.
  • Completed: Evento que é disparado quando a operação foi finalizada.

Com a instância do DispatcherOperation em mãos, podemos utilizar duas formas para chegar até o resultado, que é via eventos ou através de polling. Utilizando o modelo de eventos, podemos nos vincular ao evento Completed, e quando a operação for finalizada, esse evento será automaticamente disparado. Já o polling consiste em testar, de tempo em tempo, se a operação finalizou ou não. Abaixo temos os dois exemplos de utilização:

new Thread(() =>
{
    int result = 2 ^ 4 * 2 + 3 / 3;
    Thread.Sleep(3000); //Simula Processo Complexo

    DispatcherOperation op =
        txt.Dispatcher.BeginInvoke(new Action<int>(r => txt.Text = r.ToString()), result);

    op.Completed += (o, args) => MessageBox.Show(“Finalizou Tudo”);
}).Start();

new Thread(() =>
{
    int result = 2 ^ 4 * 2 + 3 / 3;
    Thread.Sleep(3000); //Simula Processo Complexo

    DispatcherOperation op =
        txt.Dispatcher.BeginInvoke(new Action<int>(r => txt.Text = r.ToString()), result);

    //Faz Algo…

    while (op.Status != DispatcherOperationStatus.Completed)
    {
        if (op.Wait(TimeSpan.FromSeconds(5)) == DispatcherOperationStatus.Completed)
        {
            //Finalizou a Atualização da UI
            break;
        }
    }
}).Start();

Tanto o método Invoke quanto o BeginInvoke possui versões (overloads) destes métodos que permitem especificar uma prioridade, e para isso, utilizamos uma das doze opções definidas pelo enumerador DispatcherPriority. Há algumas opções interessantes, como por exemplo Inactive, que permite você alistar a operação, mas que não será processada. De qualquer forma, na maioria das vezes a opção Normal (que também é o padrão), já será o suficiente. Abaixo um exemplo de como podemos proceder para especificar a prioridade de uma operação:

txt.Dispatcher.BeginInvoke(new Action<int>(r => txt.Text = r.ToString()), DispatcherPriority.Normal, result);

Uma vez que você tem operações alistadas no Dispatcher, o runtime irá determinar quando executá-las, dependendo da prioridade definida. A classe Dispatcher também fornece métodos para abortar todas as operações pendentes de processamento. Para isso, recorremos aos métodos InvokeShutdown ou BeginInvokeShutdown (a diferença entre eles já é sabida). A classe Dispatcher ainda fornece um evento chamado ShutdownFinished, que é disparado quando shutdown do Dispatcher for completamente finalizado, e com isso, você poderá tomar alguma decisão.

Conclusão: Como vimos, ambas tecnologias (Windows Forms e WPF) possuem os mesmos comportamentos. Como trabalhar com ambientes multi-threading não é uma tarefa fácil, a Microsoft introduziu no WPF uma forma diferente de se trabalhar para a atualizar de UI através de uma segunda thread. Além de um modelo suavemente diferente, há algumas melhorias internas que garantem que isso acabe sendo executado de uma forma melhor. E para finalizar, essa técnica visa sempre tornar a aplicação mais amigável para o usuário, sem que eles tenha experiências ruins.