我有一个带有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)概述了并发运行时和各种选项。几个示例和许多其他材料的链接。
答案 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::Normal
或CoreDispatcherPriority::Low
,以及 确保任何链式回调也使用CoreDispatcherPriority::Normal
或CoreDispatcherPriority::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更新,则列表框可能看起来很奇怪 绘画期间的背景线程。