Migração da funcionalidade de encadeamento

Este tópico descreve como migrar o código de threading em um aplicativo UWP (Plataforma Universal do Windows) para o SDK do Aplicativo Windows.

Resumo das diferenças de API e/ou recursos

O modelo de threading da UWP é uma variação do modelo de apartamento de thread único (STA) chamado Application STA (ASTA), que bloqueia a reentrada e ajuda a evitar vários bugs e impasses de reentrada. Um thread ASTA também é conhecido como thread de interface do usuário.

O SDK do Aplicativo Windows usa um modelo de threading STA padrão, que não fornece as mesmas proteções de reentrada.

O tipo CoreDispatcher migra para DispatcherQueue. E o método CoreDispatcher.RunAsync migra para DispatcherQueue.TryEnqueue.

C++/WinRT. Se você estiver usando winrt::resume_foreground com CoreDispatcher, migre-o para usar DispatcherQueue .

Modelo de threading ASTA para STA

Para obter mais detalhes sobre o modelo de threading ASTA, consulte a postagem do blog O que há de tão especial no aplicativo STA?.

Como o modelo de threading STA do SDK do aplicativo Windows não tem as mesmas garantias em relação à prevenção de problemas de reentrada, se seu aplicativo UWP assumir o comportamento não reentrante do modelo de threading ASTA, seu código poderá não se comportar conforme o esperado.

Uma coisa a ser observada é a reentrada nos controles XAML (veja o exemplo em Uma migração do SDK do aplicativo Windows do aplicativo de amostra UWP Photo Editor (C++/WinRT)). E para algumas falhas, como violações de acesso, a pilha de chamadas de falha direta geralmente é a pilha certa a ser usada. Mas se for uma falha de exceção armazenada — que tem código de exceção: 0xc000027b — será necessário mais trabalho para obter a pilha de chamadas correta.

Exceções armazenadas

As falhas de exceção armazenadas evitam um possível erro e isso será usado posteriormente se nenhuma parte do código tratar a exceção. Às vezes, o XAML decide que o erro é fatal imediatamente; nesse caso, a pilha de falha direta pode ser boa. Porém, com mais frequência, a pilha se desenrolou antes de ser considerada fatal. Para obter mais detalhes sobre exceções suspensas, consulte o episódio do Inside Show Exceção Suspensa C000027B.

Para falhas de exceção armazenadas (para ver uma bomba de mensagem aninhada ou para ver a exceção específica do controle XAML sendo lançada), você pode obter mais informações sobre a falha carregando um despejo de memória no Windows Debugger (WinDbg) (consulte Baixar ferramentas de depuração para Windows) e, em seguida, usando !pde.dse para despejar as exceções armazenadas.

A extensão do depurador PDE (para o !pde.dse comando) é uma extensão WinDbg fornecida pela comunidade. Pesquise por "extensão pde WinDbg" para localizar o local de download atual. Coloque o x64 ou x86 .dll apropriado do pacote no diretório winext da instalação do WinDbg e então !pde.dse funcionará em despejos de memória de exceção armazenados.

Frequentemente, haverá várias exceções armazenadas, com algumas no final que foram tratadas/ignoradas. Mais comumente, a primeira exceção armazenada é a interessante. Em alguns casos, a primeira exceção armazenada pode ser um relançamento da segunda; portanto, se a segunda exceção armazenada aparecer mais profundamente na mesma pilha que a primeira, a segunda exceção poderá ser a origem do erro. O código de erro mostrado com cada exceção armazenada também é valioso, pois fornece o HRESULT associado a essa exceção.

Alterar Windows.UI.Core.CoreDispatcher para Microsoft.UI.Dispatching.DispatcherQueue

Esta seção se aplica se você estiver usando a classe Windows.UI.Core.CoreDispatcher em seu aplicativo UWP. Isso inclui o uso de quaisquer métodos ou propriedades que aceitam ou retornam um CoreDispatcher, como as propriedades DependencyObject.Dispatcher e CoreWindow.Dispatcher. Por exemplo, você chamará DependencyObject.Dispatcher ao recuperar o CoreDispatcher pertencente a um Windows.UI.Xaml.Controls.Page.

// MainPage.xaml.cs in a UWP app
if (this.Dispatcher.HasThreadAccess)
{
    ...
}
// MainPage.xaml.cpp in a UWP app
if (this->Dispatcher().HasThreadAccess())
{
    ...
}

Em vez disso, no seu aplicativo do SDK do Aplicativo Windows, você precisará usar a classe Microsoft.UI.Dispatching.DispatcherQueue. E os métodos ou propriedades correspondentes que aceitam ou retornam uma DispatcherQueue, como as propriedades DependencyObject.DispatcherQueue e Microsoft.UI.Xaml.Window.DispatcherQueue. Por exemplo, você chamará DependencyObject.DispatcherQueue ao recuperar o DispatcherQueue pertencente a um Microsoft.UI.Xaml.Controls.Page (a maioria dos objetos XAML são DependencyObjects).

// MainPage.xaml.cs in a Windows App SDK app
if (this.DispatcherQueue.HasThreadAccess)
{
    ...
}
// MainPage.xaml.cpp in a Windows App SDK app
#include <winrt/Microsoft.UI.Dispatching.h>
...
if (this->DispatcherQueue().HasThreadAccess())
{
    ...
}

Alterar CoreDispatcher.RunAsync para DispatcherQueue.TryEnqueue

Esta seção se aplica se você estiver usando o método Windows.UI.Core.CoreDispatcher.RunAsync para agendar uma tarefa a ser executada no thread principal da interface do usuário (ou no thread associado a um Windows.UI.Core.CoreDispatcher específico).

// MainPage.xaml.cs in a UWP app
public void NotifyUser(string strMessage)
{
    if (this.Dispatcher.HasThreadAccess)
    {
        StatusBlock.Text = strMessage;
    }
    else
    {
        var task = this.Dispatcher.RunAsync(
            Windows.UI.Core.CoreDispatcherPriority.Normal,
            () => StatusBlock.Text = strMessage);
    }
}
// MainPage.cpp in a UWP app
void MainPage::NotifyUser(std::wstring strMessage)
{
    if (this->Dispatcher().HasThreadAccess())
    {
        StatusBlock().Text(strMessage);
    }
    else
    {
        auto task = this->Dispatcher().RunAsync(
            Windows::UI::Core::CoreDispatcherPriority::Normal,
            [strMessage, this]()
            {
                StatusBlock().Text(strMessage);
            });
    }
}

Em seu aplicativo SDK do Aplicativo Windows, use o método Microsoft.UI.Dispatching.DispatcherQueue.TryEnqueue. Ele adiciona ao Microsoft.UI.Dispatching.DispatcherQueue uma tarefa que será executada no thread associado ao DispatcherQueue.

// MainPage.xaml.cs in a Windows App SDK app
public void NotifyUser(string strMessage)
{
    if (this.DispatcherQueue.HasThreadAccess)
    {
        StatusBlock.Text = strMessage;
    }
    else
    {
        bool isQueued = this.DispatcherQueue.TryEnqueue(
        Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal,
        () => StatusBlock.Text = strMessage);
    }
}
// MainPage.xaml.cpp in a Windows App SDK app
#include <winrt/Microsoft.UI.Dispatching.h>
...
void MainPage::NotifyUser(std::wstring strMessage)
{
    if (this->DispatcherQueue().HasThreadAccess())
    {
        StatusBlock().Text(strMessage);
    }
    else
    {
        bool isQueued = this->DispatcherQueue().TryEnqueue(
            Microsoft::UI::Dispatching::DispatcherQueuePriority::Normal,
            [strMessage, this]()
            {
                StatusBlock().Text(strMessage);
            });
    }
}

Migrar winrt::resume_foreground (C++/WinRT)

Esta seção se aplica se você usar a função winrt::resume_foreground em uma corrotina em seu aplicativo UWP C++/WinRT.

Na UWP, o caso de uso de winrt::resume_foreground é mudar a execução para uma thread em primeiro plano (essa thread em primeiro plano geralmente é a associada a um Windows.UI.Core.CoreDispatcher). Aqui está um exemplo disso.

// MainPage.cpp in a UWP app
winrt::fire_and_forget MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    ...
    co_await winrt::resume_foreground(this->Dispatcher());
    ...
}

No seu aplicativo SDK do Aplicativo Windows:

Portanto, primeiro adicione uma referência ao pacote NuGet Microsoft.Windows.ImplementationLibrary .

Em seguida, adicione o seguinte include a pch.h no projeto de destino.

#include <wil/cppwinrt_helpers.h>

E então siga o padrão mostrado abaixo.

// MainPage.xaml.cpp in a Windows App SDK app
...
winrt::fire_and_forget MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    ...
    co_await wil::resume_foreground(this->DispatcherQueue());
    ...
}

Confira também