线程功能迁移

本主题介绍如何将通用 Windows 平台 (UWP) 应用程序中的线程代码迁移到Windows 应用 SDK。

API 和/或功能差异摘要

UWP 的线程模型是单线程单元 (STA) 模型(称为 Application STA (ASTA) )的变体,可阻止重入,并帮助避免各种重入 bug 和死锁。 ASTA 线程也称为 UI 线程。

Windows 应用 SDK使用标准 STA 线程模型,该模型不提供相同的重新进入安全措施。

CoreDispatcher 类型会迁移到 DispatcherQueue。 并且 CoreDispatcher.RunAsync 方法也会迁移到 DispatcherQueue.TryEnqueue。

C++/WinRT。 如果将 winrt::resume_foreground 与 CoreDispatcher 一起使用,请将其迁移为改用 DispatcherQueue。

ASTA 到 STA 线程模型

有关 ASTA 线程模型的更多详细信息,请参阅博客文章 Application STA 的特别之处是什么?

由于Windows 应用 SDK的 STA 线程模型在防止重入问题方面没有相同的保证,因此,如果 UWP 应用假定 ASTA 线程模型的非重新进入行为,则代码的行为可能不符合预期。

需要注意的一点是重新进入 XAML 控件(请参阅 UWP 照片编辑器示例应用 (C++/WinRT) 的 Windows 应用 SDK 迁移中的示例)。 对于某些崩溃(例如访问冲突),直接崩溃调用堆栈通常是要使用的正确堆栈。 但如果它是存放异常崩溃(异常代码为 0xc000027b),则需要更多的工作来获取正确的调用堆栈。

存放异常

存放异常崩溃保存了一个可能的错误,以后如果没有部分代码处理异常,系统便会使用该错误。 XAML 有时会立即判定错误是致命的,在这种情况下,直接崩溃堆栈可能是好的。 但更常见的情况是,堆栈在被确定为致命之前就已展开。 有关存放异常的详细信息,请参阅 Inside 节目剧集存放异常 C000027B

对于存放异常崩溃,(查看嵌套的消息泵,或查看引发的 XAML 控件的特定异常)可以通过在 Windows 调试程序 (WinDbg) 中加载故障转储来获取有关崩溃的详细信息。请参阅下载适用于 Windows 的调试工具,然后使用 !pde.dse 来转储存放异常。

PDE 调试程序扩展(用于 !pde.dse 命令)可通过从 OneDrive 下载 PDE*.zip 文件获取。 将该 zip 文件中的相应 x64 或 x86 .dllwinext 置于 WinDbg 安装的目录中,然后 !pde.dse 将处理存放异常故障转储。

通常会有多个存放异常,其中最后的一些异常已处理/已忽略。 最常见的情况是,第一个存放异常是耐人寻味的异常。 在某些情况下,第一个存放异常可能是再次引发的第二个异常,因此,如果第二个存放异常显示在与第一个相同的堆栈中更深的位置,那么第二个异常可能是错误的根源。 随每个存放异常一起显示的错误代码也很有用,因为它提供了与该异常关联的 HRESULT。

将 Windows.UI.Core.CoreDispatcher 更改为 Microsoft.UI.Dispatching.DispatcherQueue

如果 UWP 应用中使用的是 Windows.UI.Core.CoreDispatcher 类,则本部分适用。 这包括使用采用或返回 CoreDispatcher 的任何方法或属性,例如 DependencyObject.DispatcherCoreWindow.Dispatcher属性。 例如,在检索属于Windows.UI.Xaml.Controls.Page的 CoreDispatcher 时,将调用DependencyObject.Dispatcher。

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

相反,在 Windows 应用 SDK 应用中,需要使用 Microsoft.UI.Dispatching.DispatcherQueue 类。 以及采用或返回 DispatcherQueue 的相应方法或属性,例如 DependencyObject.DispatcherQueueMicrosoft.UI.Xaml.Window.DispatcherQueue 属性。 例如,在检索属于 Microsoft.UI.Xaml.Controls.Page(大多数 XAML 对象都是 DependencyObject)的 DispatcherQueue 时,将调用DependencyObject.DispatcherQueue

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

将 CoreDispatcher.RunAsync 更改为 DispatcherQueue.TryEnqueue

如果使用 Windows.UI.Core.CoreDispatcher.RunAsync 方法计划要在主 UI 线程(或与特定 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);
            });
    }
}

在 Windows 应用 SDK 应用中,则需改用 Microsoft.UI.Dispatching.DispatcherQueue.TryEnqueue 方法。 它会将要在与 DispatcherQueue 关联的线程上运行的任务添加到 Microsoft.UI.Dispatching.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);
            });
    }
}

迁移 winrt::resume_foreground (C++/WinRT)

如果在 C++/WinRT UWP 应用的协同程序中使用 winrt::resume_foreground 函数,则本部分适用。

在 UWP 中,winrt::resume_foreground 的用例是将执行切换到前台线程(前台线程通常是与 Windows.UI.Core.CoreDispatcher 相关联的线程)。 下面是该线程的一个示例。

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

在 Windows 应用 SDK 应用中:

因此,请首先添加对 Microsoft.Windows.ImplementationLibrary NuGet 包的引用。

然后,将以下 include 添加到目标项目的 pch.h 中。

#include <wil/cppwinrt_helpers.h>

接下来,请遵循下面所示的模式。

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