Migrazione delle funzionalità di threading

Questo argomento descrive come eseguire la migrazione del codice di threading in un'applicazione Universal Windows Platform (UWP) a Windows App SDK.

Riepilogo delle differenze tra API e/o funzionalità

Il modello di threading della piattaforma UWP è una variante del modello apartment a thread singolo (STA) denominato APPLICATION STA (ASTA), che blocca la reentrancy e consente di evitare vari bug di reentrancy e deadlock. Un thread ASTA è anche detto thread dell'interfaccia utente.

Windows App SDK utilizza un modello di threading STA standard, che non fornisce le stesse misure di sicurezza per la reentrancy.

Il tipo tipo CoreDispatcher esegue la migrazione a DispatcherQueue. E il metodo CoreDispatcher.RunAsync esegue la migrazione a DispatcherQueue.TryEnqueue.

C++/WinRT. Se si usa winrt::resume_foreground con CoreDispatcher, eseguire invece la migrazione per usare DispatcherQueue.

Modello di threading da ASTA a STA

Per altri dettagli sul modello di threading ASTA, vedere il post di blog Cosa c'è di speciale nell'Application STA?.

Poiché il modello di threading STA di Windows App SDK non ha le stesse garanzie relative alla prevenzione dei problemi di reentrancy, se l'app UWP presuppone il comportamento non rientrante del modello di threading ASTA, il codice potrebbe non comportarsi come previsto.

Una cosa da tenere presente è la reentrancy nei controlli XAML (vedere l'esempio in Migrazione a Windows App SDK dell'app di esempio Photo Editor UWP (C++/WinRT)). E per alcuni arresti anomali, ad esempio violazioni di accesso, lo stack di chiamate di arresto anomalo diretto è in genere lo stack corretto da usare. Ma è un arresto anomalo con eccezione non gestita, che ha un codice di eccezione: 0xc000027b, quindi serve più lavoro per ottenere il giusto stack di chiamate.

Eccezioni non gestite

Gli arresti anomali con eccezione non gestita salvano un possibile errore e ciò viene usato in un secondo momento se nessuna parte del codice gestisce l'eccezione. XAML a volte decide immediatamente che l'errore è irreversibile, nel qual caso lo stack di arresto anomalo diretto potrebbe essere valido. Ma più frequentemente lo stack viene rimosso prima di essere determinato irreversibile. Per altre informazioni sulle eccezioni non gestite, vedere l'episodio di Inside Show Eccezione non gestita C000027B.

Per gli arresti anomali con eccezioni non gestite (per visualizzare un message pump nidificato o per visualizzare l'eccezione specifica del controllo XAML generata), è possibile ottenere altre informazioni sull'arresto anomalo del sistema caricando un dump di arresto anomalo nel debugger di Windows (WinDbg) (vedere Scaricare gli strumenti di debug per Windows) e quindi usare !pde.dse per eseguire il dump delle eccezioni non gestite.

L'estensione del debugger PDE (per il comando !pde.dse) è disponibile scaricando il file PDE*.zip da OneDrive. Inserire il file x64 o x86 .dll appropriato da tale file ZIP nella winext directory dell'installazione di WinDbg e quindi !pde.dse funzionerà sui dump di arresto anomalo dell'eccezione stowed.

Spesso ci saranno più eccezioni non gestite, con alcune alla fine gestite/ignorate. Più comunemente, la prima eccezione non gestita è quella interessante. In alcuni casi, la prima eccezione non gestita potrebbe essere una rigenerazione della seconda, quindi se la seconda eccezione non gestita appare più profonda nello stesso stack della prima, la seconda eccezione potrebbe essere l'origine dell'errore. Anche il codice di errore visualizzato con ogni eccezione non gestita è utile, poiché fornisce il valore HRESULT associato all'eccezione.

Modificare Windows.UI.Core.CoreDispatcher in Microsoft.UI.Dispatching.DispatcherQueue

Questa sezione si applica se si utilizza la classe classe Windows.UI.Core.CoreDispatcher nell'app UWP. Include l'uso di ogni metodo o proprietà che acquisisce o restituisce un CoreDispatcher, come le proprietà DependencyObject.Dispatcher e CoreWindow.Dispatcher. Ad esempio, si chiamerà DependencyObject.Dispatcher quando si recupera il CoreDispatcher che appartiene a 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())
{
    ...
}

Nell'app Windows App SDK è invece necessario utilizzare la classe Microsoft.UI.Dispatching.DispatcherQueue. E i corrispondenti metodi o proprietà che acquisiscono o restituiscono un DispatcherQueue, come le proprietà DependencyObject.DispatcherQueue e Microsoft.UI.Xaml.Window.DispatcherQueue. Ad esempio, si chiamerà DependencyObject.DispatcherQueue quando si recupera il DispatcherQueue che appartiene a Microsoft.UI.Xaml.Controls.Page (la maggior parte degli oggetti XAML sono 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())
{
    ...
}

Modificare CoreDispatcher.RunAsync in DispatcherQueue.TryEnqueue

Questa sezione si applica se si utilizza il metodo Windows.UI.Core.CoreDispatcher.RunAsync per pianificare l'esecuzione di un'attività sul thread dell'interfaccia utente principale (o su un thread associato a un particolare Windows.UI.Core.CoreDispatcher).

// 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);
            });
    }
}

Nell'app Windows App SDK, usare invece il metodo Microsoft.UI.Dispatching.DispatcherQueue.TryEnqueue). Aggiunge al Microsoft.UI.Dispatching.DispatcherQueue un'attività che verrà eseguita sul thread associato al 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);
            });
    }
}

Migrazione di winrt::resume_foreground (C++/WinRT)

Questa sezione si applica se si utilizza la funzione winrt::resume_foreground in una coroutine nell'app UWP C++/WinRT.

Nella piattaforma UWP, il caso d'uso per winrt::resume_foreground consiste nel passaggio all'esecuzione in un thread in primo piano (spesso quello associato a un Windows.UI.Core.CoreDispatcher). Ecco un esempio:

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

Nell'app Windows App SDK:

Aggiungere quindi prima un riferimento al paccheto NuGet Microsoft.Windows.ImplementationLibrary.

Aggiungere quindi l'include seguente a pch.h nel progetto di destinazione.

#include <wil/cppwinrt_helpers.h>

E quindi seguire il modello illustrato di seguito.

// 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());
    ...
}

Vedi anche