从PPL任务继续更新UWP UI - 警告C4451

时间:2018-04-05 16:06:09

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

我有一个带有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)的解释,其中提到了警告,#34;编译时警告消耗非敏捷类(C4451)&#34;但是我不确定我是否有实际问题。

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

我正在使用DispatchedHandler()来从任务延续中获取对UI线程的访问权限。如果我尝试使用myTextBlock->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 < 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)概述了并发运行时和各种选项。几个示例和许多其他材料的链接。

1 个答案:

答案 0 :(得分:0)

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

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控件的进程。这有两个   后果。首先,要使应用程序保持响应,所有   不应在ASTA线程上运行CPU密集型和I / O操作。   其次,必须封送来自后台线程的结果   回到ASTA更新UI。在C ++ Windows 8.x商店应用中,   MainPage和其他XAML页面都在ATSA上运行。因此,任务   默认情况下,在ASTA上声明的延续会在那里运行   所以你可以直接在continuation体中更新控件。然而,   如果您将任务嵌套在另一个任务中,那么嵌套的任何延续   任务在MTA中运行。因此,您需要考虑是否   明确指定这些延续运行的上下文。

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

消除警告

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

编译器警告与通过工作线程中的非Agile Dispatcher对象访问UI线程的CoreWindow有关,而此版本获取对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控件的进程。这有两个   后果。首先,要使应用程序保持响应,所有   不应在ASTA线程上运行CPU密集型和I / O操作。   其次,必须封送来自后台线程的结果   回到ASTA更新UI。在C ++ Windows 8.x商店应用中,   MainPage和其他XAML页面都在ATSA上运行。因此,任务   默认情况下,在ASTA上声明的延续会在那里运行   所以你可以直接在continuation体中更新控件。然而,   如果您将任务嵌套在另一个任务中,那么嵌套的任何延续   任务在MTA中运行。因此,您需要考虑是否   明确指定这些延续运行的上下文。

使用Windows Runtime引入了敏捷和非敏捷线程的概念。 Microsoft Docs article Threading and Marshaling (C++/CX)为C ++程序员提供了一个介绍。

  

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

另见

Threading Model讨论了WPF线程模型。

  

从历史上看,Windows只允许用户访问UI元素   创建它们的线程。这意味着后台线程在   某些长时间运行的任务的收费无法更新文本框   完了。 Windows这样做是为了确保UI组件的完整性。   如果列表框的内容由a更新,则列表框可能看起来很奇怪   绘画期间的背景线程。

关于COM Threading Models from The Open Group

的一个很好的解释