【问题标题】:updating UWP UI from PPL task continuation - warning C4451从 PPL 任务继续更新 UWP UI - 警告 C4451
【发布时间】:2018-04-10 20:55:31
【问题描述】:

我有一个带有 C++ 和 Visual Studio 2017 社区版的示例 UWP 应用,我正在努力了解 PPL 功能。

我生成了一个 UWP 应用,然后对 MainPage.xaml.cpp 文件进行了以下修改。这些更改的目的是模拟一个耗时数秒的异步操作,并在操作完成各个阶段时更新显示的 UI。

这可行,并且 UI 已更新。

但是我在编译时确实看到了以下警告。

1> ... \appuwp1\mainpage.xaml.cpp(46): warning C4451: 'AppUwp1::MainPage::{ctor}::<lambda_df2e69e2b6fe4b1dfba3f26ad0398a3e>::myThread': Usage of ref class 'Windows::UI::Core::CoreWindow' inside this context can lead to invalid marshaling of object across contexts
1> ... \appuwp1\mainpage.xaml.cpp(46): note: Consider using 'Platform::Agile<Windows::UI::Core::CoreWindow>' instead
1> ... \appuwp1\mainpage.xaml.cpp(56): warning C4451: 'AppUwp1::MainPage::{ctor}::<lambda_c1468d2f6468239bd456bea931167a21>::myThread': Usage of ref class 'Windows::UI::Core::CoreWindow' inside this context can lead to invalid marshaling of object across contexts
1> ... \appuwp1\mainpage.xaml.cpp(56): note: Consider using 'Platform::Agile<Windows::UI::Core::CoreWindow>' instead

这些警告是什么意思?

我确实找到了Threading and Marshaling (C++/CX) 的解释,其中提到了警告“使用非敏捷类 (C4451) 时的编译器警告”,但是我不确定我是否有实际问题。

是否有不同的、更可接受的方式从任务延续更新 UI?

我正在使用DispatchedHandler() 以便从任务继续访问 UI 线程。如果我尝试使用 myTextBlock-&gt;Text = "this is my text and some more text after sleep"; 而不将其包装在 DispatchedHandler() 中,则会出现异常。该异常是可以理解的,因为 then 任务延续不再在 UI 线程中运行。

这个stackoverflow,Warning C4451: Usage of ref class BackgroundTaskDeferral can lead to invalid marshaling 表示使用Platform:Agile 确实解决了他们的警告。

但是没有解释警告的实际含义

初始任务创建除了启动处理异步操作的线程之外什么都不做。每个then 延续子句都执行Sleep() 来表示一些需要时间的操作,然后用消息更新显示的UI 屏幕。

MainPage::MainPage()
{
    InitializeComponent();

    myTextBlock->Text = "this is my text and some more text";
    auto myThread = CoreWindow::GetForCurrentThread();

    concurrency::create_task ([=]() {
        // we are wanting to spin off a task that will be
        // performed asynchronously and the real work is done in the
        // following task continuations.
        Sleep(5000);
    }).then([=]()
    {
        Sleep(5000);
        myThread->Dispatcher->RunAsync(
            CoreDispatcherPriority::Normal,
            ref new DispatchedHandler([=]()
        {
            // Do stuff on the UI Thread
            myTextBlock->Text = "this is my text and some more text after sleep";
        }));
    }).then([=]()        // warning C4451 for this line
    {
        Sleep(5000);
        myThread->Dispatcher->RunAsync(
            CoreDispatcherPriority::Normal,
            ref new DispatchedHandler([=]()
        {
            // Do stuff on the UI Thread
            myTextBlock->Text = "this is my text and some more text after sleep after sleep again";
        }));
    });             // warning C4451 for this line
}

额外探索#1

随着MainPage::MainPage() 的以下更改,我看到 UI 窗口中显示了预期的一系列消息。在几秒钟的过程中显示了一系列文本字符串,包括一系列以iCount的递增值开头的字符串,这些字符串在第一个任务继续的循环中生成。

似乎如果for (int iCount = 0; iCount &lt; 3; iCount++) { 被放置在new DispatchedHandler() lambda 中,它会导致 UI 线程阻塞数秒并且 UI 变得无响应,然后显示第二个任务继续的文本字符串,并且UI 再次变得响应。如果for 在此源代码示例中位于外部,则 UI 线程不会被阻塞并且 UI 保持响应。

这是否意味着 new DispatchedHandler() 中包含的 lambda 被交给 UI 线程运行?

MainPage::MainPage()
{
    InitializeComponent();

    myTextBlock->Text = "this is my text and some more text";
    auto myThread = CoreWindow::GetForCurrentThread();

    concurrency::create_task ([=]() {

        Sleep(2000);
        myThread->Dispatcher->RunAsync(
            CoreDispatcherPriority::Normal,
            ref new DispatchedHandler([=]()
        {
            myTextBlock->Text = "start of task";

            // Do stuff on the UI Thread
        }));
    }).then([=]()
    {
        Sleep(5000);
        for (int iCount = 0; iCount < 3; iCount++) {
            myThread->Dispatcher->RunAsync(
                CoreDispatcherPriority::Normal,
                ref new DispatchedHandler([=]()
                {
                    // Do stuff on the UI Thread
                    std::wstringstream ss;

                    ss << iCount << " text first";
                    myTextBlock->Text = ref new Platform::String(ss.str().c_str());
                }   )   // close off the DispatchedHandler() lambda
            );          // close off the RunAsync()
            Sleep(2000);
        }               // close off for loop
    }).then([=]()
    {
        Sleep(5000);
        myThread->Dispatcher->RunAsync(
            CoreDispatcherPriority::Normal,
            ref new DispatchedHandler([=]()
        {
            // Do stuff on the UI Thread
            myTextBlock->Text = "this is my text and some more text after sleep after sleep again";
        }));
    });
}

补充说明

MVVM and Accessing the UI Thread in Windows Store Apps

Running WPF Application with Multiple UI Threads

另请参阅其他 stackoverflow 帖子:

MSDN article: Concurrency Runtime

Task Parallelism (Concurrency Runtime) 提供并发运行时和各种选项的概述。几个示例和大量指向其他材料的链接。

【问题讨论】:

  • 您的第一个评论块不正确 - 它在线程池线程上运行。此外,您不应该在函数的开头使用Sleep(2000),因为它确实在 UI 线程上。编译器警告您,如果您从错误的线程中使用CoreWindow,它将失败。 Dispatcher 用法可确保您不会那样做。
  • @PeterTorr-MSFT 对不起,但我真的不觉得你的评论对我的实际问题很有帮助。我对评论块进行了更改并移动了Sleep(),但是我仍然想要一些资源,其中包含有关从另一个线程访问 UI 的更好信息。
  • 我的最后两句话解释了您收到警告的原因以及您的代码为何有效。 Dispatcher-&gt;RunAsync 调用确保工作在适当的线程上完成。您可以阅读有关Marshalling on WikipediaCOM threading 的更多信息。
  • 第二个示例中的不同之处在于您将 CoreDispatcher(敏捷)传递给 lambda,但在第一个示例中,您传递了整个 CoreWindow(这不是敏捷)。
  • @PeterTorr-MSFT 感谢您的确认。我认为可能是这种情况,并且打算对敏捷和非敏捷做一些额外的研究。为了回答这个发布的问题stackoverflow.com/questions/49642684/…,我在研究中得到了一些关注,这似乎有点相关。与其说是问题,不如说是专门针对 Task 的研究。

标签: visual-c++ uwp task-parallel-library


【解决方案1】:

这些来自编译器的警告并不意味着你做错了什么,它意味着你可能做错了什么。

1> ... \appuwp1\mainpage.xaml.cpp(46): warning C4451: 'AppUwp1::MainPage::{ctor}::<lambda_df2e69e2b6fe4b1dfba3f26ad0398a3e>::myThread': Usage of ref class 'Windows::UI::Core::CoreWindow' inside this context can lead to invalid marshaling of object across contexts
1> ... \appuwp1\mainpage.xaml.cpp(46): note: Consider using 'Platform::Agile<Windows::UI::Core::CoreWindow>' instead

CoreWindow Class 有如下注释:

这个类不是敏捷的,这意味着你需要考虑它的 线程模型和编组行为。如需更多信息,请参阅Threading and Marshaling (C++/CX)

Creating Asynchronous Operations in C++ for Windows Store Apps

Windows 运行时使用 COM 线程模型。在这个模型中, 对象托管在不同的公寓中,具体取决于它们的方式 处理它们的同步。线程安全对象托管在 多线程单元(MTA)。必须由 a 访问的对象 单线程托管在单线程单元 (STA) 中。

在具有 UI 的应用中,ASTA(应用程序 STA)线程是 负责泵送窗口消息,并且是 可以更新 STA 托管的 UI 控件的进程。这有两个 结果。首先,为了使应用程序保持响应,所有 CPU 密集型和 I/O 操作不应在 ASTA 线程上运行。 其次,必须对来自后台线程的结果进行编组 返回 ASTA 以更新 UI。在 C++ Windows 8.x Store 应用程序中, MainPage 和其他 XAML 页面都在 ATSA 上运行。因此,任务 在 ASTA 上声明的延续默认情况下在那里运行 因此您可以直接在延续正文中更新控件。然而, 如果您将一个任务嵌套在另一个任务中,则该嵌套的任何延续 任务在 MTA 中运行。因此,您需要考虑是否 明确指定这些延续在什么上下文中运行。

是的,编写源代码的方式略有不同,以消除警告。

消除警告

如果我修改 MainPage::MainPage() 的源,而不是在由concurrency::create_task() 启动的工作线程中使用CoreWindow::GetForCurrentThread(); 提供的Windows::UI::Core::CoreWindow ^,而是从 UI 线程本身获取Dispatcher然后在工作线程中使用Dispatcher 对象,我不再收到警告。这是因为Windows::UI::Core::CoreWindow 不是敏捷的,所以CoreWindow 对象来自的线程必须是一个考虑因素。但是Dispatcher 对象是敏捷的。

编译器警告与通过工作线程内的非敏捷 CoreWindow 对象访问 UI 线程的 Dispatcher 有关,而此版本在 UI 线程中获取对 UI 线程调度程序的引用然后使用 Dispatcher 引用,编译器就可以了。

此版本的源代码如下所示:

MainPage::MainPage()
{
    InitializeComponent();
    myTextBlock->Text = "this is my text and some more text";
    auto myDispatcher = CoreWindow::GetForCurrentThread()->Dispatcher;

    concurrency::create_task([=]() {
        Sleep(2000);
        myDispatcher->RunAsync(
            CoreDispatcherPriority::Normal,
            ref new DispatchedHandler([=]()
        {
            myTextBlock->Text = "start of task";

            // Do stuff on the UI Thread
        }));
    }).then([=]()
    {
        Sleep(5000);
        for (int iCount = 0; iCount < 3; iCount++) {
            myDispatcher->RunAsync(
                CoreDispatcherPriority::Normal,
                ref new DispatchedHandler([=]()
            {
                // Do stuff on the UI Thread
                std::wstringstream ss;

                ss << iCount << " text first";
                myTextBlock->Text = ref new Platform::String(ss.str().c_str());
            })   // close off the DispatchedHandler() lambda
            );          // close off the RunAsync()
            Sleep(2000);
        }               // close off for loop
    }).then([=]()
    {
        Sleep(5000);
        myDispatcher->RunAsync(
            CoreDispatcherPriority::Normal,
            ref new DispatchedHandler([=]()
        {
            // Do stuff on the UI Thread
            myTextBlock->Text = "this is my text and some more text after sleep after sleep again";
        }));
    });
}

CoreDispatcher.RunAsync(CoreDispatcherPriority, DispatchedHandler) Method有如下备注:

如果您在工作线程上并希望在 UI 上安排工作 线程,使用CoreDispatcher::RunAsync。始终将优先级设置为 CoreDispatcherPriority::NormalCoreDispatcherPriority::Low,和 确保任何链接的回调也使用 CoreDispatcherPriority::NormalCoreDispatcherPriority::Low

线程与异步和敏捷方法的背景

大部分 .NET 功能和 Windows 运行时功能以及越来越多的通用功能都以 COM 控件和功能的形式提供。使用 COM 技术可以让多种语言、平台和技术使用相同的功能。

然而,与 COM 技术一起存在的复杂性也很大,幸运的是,可以通过使用各种特定语言的包装器对其进行封装,从而在很大程度上隐藏这些复杂性。

COM 技术的一个考虑因素是公寓的想法。 MSDN article Processes, Threads, and Apartments 提供了对该主题的技术性介绍。

Creating Asynchronous Operations in C++ for Windows Store Apps

Windows 运行时使用 COM 线程模型。在这个模型中, 对象托管在不同的公寓中,具体取决于它们的方式 处理它们的同步。线程安全对象托管在 多线程单元(MTA)。必须由 a 访问的对象 单线程托管在单线程单元 (STA) 中。

在具有 UI 的应用中,ASTA(应用程序 STA)线程是 负责泵送窗口消息,并且是 可以更新 STA 托管的 UI 控件的进程。这有两个 结果。首先,为了使应用程序保持响应,所有 CPU 密集型和 I/O 操作不应在 ASTA 线程上运行。 其次,必须对来自后台线程的结果进行编组 返回 ASTA 以更新 UI。在 C++ Windows 8.x Store 应用程序中, MainPage 和其他 XAML 页面都在 ATSA 上运行。因此,任务 在 ASTA 上声明的延续默认情况下在那里运行 因此您可以直接在延续正文中更新控件。然而, 如果您将一个任务嵌套在另一个任务中,则该嵌套的任何延续 任务在 MTA 中运行。因此,您需要考虑是否 明确指定这些延续在什么上下文中运行。

在 Windows 运行时引入了敏捷和非敏捷线程的概念。 Microsoft Docs article Threading and Marshaling (C++/CX) 为 C++ 程序员提供了介绍。

在绝大多数情况下,Windows 运行时类的实例, 像标准 C++ 对象一样,可以从任何线程访问。这样的 类被称为“敏捷”。但是,少数 Windows Windows 附带的运行时类是非敏捷的,必须是 与标准 C++ 对象相比,它更像 COM 对象。你不 需要成为 COM 专家才能使用非敏捷类,但您确实需要 考虑到类的线程模型及其编组 行为。本文为那些稀有的人提供背景和指导 需要使用非敏捷实例的场景 类。

另请参阅

Threading Model 讨论 WPF 线程模型。

从历史上看,Windows 允许 UI 元素只能由 创建它们的线程。这意味着一个后台线程在 一些长时间运行的任务的负责无法更新文本框 完成的。 Windows 这样做是为了确保 UI 组件的完整性。 如果列表框的内容由 绘制期间的后台线程。

关于COM Threading Models from The Open Group的一个很好的解释

【讨论】:

    猜你喜欢
    • 2014-08-03
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-08
    • 1970-01-01
    • 1970-01-01
    • 2017-06-22
    相关资源
    最近更新 更多