Linguaggio

Migrazione della funzionalità di gestione dei thread

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

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.

Il SDK per app di Windows usa un modello di threading standard STA, che non fornisce le stesse garanzie di rientranza.

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 SDK per app di Windows non offre le stesse garanzie nel prevenire i problemi di rientranza, se la tua app UWP presuppone il comportamento non rientrante del modello di threading ASTA, il codice potrebbe non comportarsi nel modo previsto.

Una cosa a cui prestare attenzione è la rientranza nei controlli XAML (si veda l'esempio in Migrazione a SDK per app di Windows dell'app di esempio UWP Photo Editor (C++/WinRT)). E per alcuni crash, ad esempio le violazioni di accesso, il callstack diretto del crash è in genere quello giusto da usare. Ma se si tratta di un arresto anomalo dovuto a una eccezione archiviata, il cui codice di eccezione è 0xc000027b, allora è necessario un lavoro aggiuntivo per ottenere lo stack di chiamate corretto.

Eccezioni archiviate

Le eccezioni archiviate in caso di arresto anomalo consentono di salvare un possibile errore, che viene poi usato 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 archiviate (per visualizzare un ciclo di messaggi nidificato o per vedere l'eccezione specifica del controllo XAML sollevata), è possibile ottenere maggiori informazioni sull'arresto anomalo caricando un dump di arresto anomalo in Windows Debugger (WinDbg) (vedere Scaricare gli strumenti di debug per Windows) e quindi usando !pde.dse per eseguire il dump delle eccezioni archiviate.

L'estensione del debugger PDE (per il !pde.dse comando) è un'estensione WinDbg fornita dalla community. Cercare "pde WinDbg extension" (estensione pde WinDbg) per trovare il percorso di download corrente. Inserire il file x64 o x86 .dll appropriato dal pacchetto nella directory winext di installazione di WinDbg e quindi !pde.dse funzionerà sui crash dump delle eccezioni stowed.

Spesso sono presenti più eccezioni archiviate, alcune delle quali, verso la fine, sono state gestite/ignorate. Nella maggior parte dei casi, la prima eccezione archiviata è quella di interesse. In alcuni casi, la prima eccezione archiviata potrebbe essere un nuovo lancio della seconda, quindi se la seconda eccezione archiviata compare più in profondità nella stessa pila di chiamate della prima, la seconda eccezione potrebbe essere all'origine dell'errore. Anche il codice di errore visualizzato con ogni eccezione archiviata è utile, in quanto fornisce l'HRESULT associato a tale eccezione.

Sostituire Windows.UI.Core.CoreDispatcher con 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 SDK per app di Windows è 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())
{
    ...
}

Sostituire CoreDispatcher.RunAsync con 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 SDK per app di Windows, 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 utilizzi la funzione winrt::resume_foreground all'interno di una coroutine nella tua 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 SDK per app di Windows:

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