从 PPL 任务继续更新 UWP UI - 警告 C4451
updating UWP UI from PPL task continuation - warning C4451
我有一个使用 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) 的解释,其中提到了警告,"Compiler warning when consuming non-agile classes (C4451)" 但是我不确定我是否遇到实际问题。
是否有不同的、更可接受的方式从任务延续更新 UI?
我正在使用 DispatchedHandler()
以便从任务继续访问 UI 线程。如果我尝试使用 myTextBlock->Text = "this is my text and some more text after sleep";
而不将其包装在 DispatchedHandler()
中,我会得到一个异常。异常是可以理解的,因为 then
任务继续在 UI 线程中不再 运行ning。
这个 Whosebug, 表明使用 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 window 中看到了预期的消息系列。在几秒钟的过程中显示了一系列文本字符串,其中包括一系列以在第一个任务继续的循环中生成的递增值 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
另请参阅其他 Whosebug 帖子:
- Run code on UI thread in WinRT
- How do I determine if I need to dispatch to UI thread in WinRT/Metro?
MSDN article: Concurrency Runtime
Task Parallelism (Concurrency Runtime) 提供并发 运行 时间和各种选项的概述。几个示例和大量指向其他 material.
的链接
来自编译器的这些警告并不意味着您做错了什么,而是意味着您可能做错了什么。
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 其中有以下注释:
This class is not agile, which means that you need to consider its
threading model and marshaling behavior. For more info, see Threading
and Marshaling
(C++/CX).
Creating Asynchronous Operations in C++ for Windows Store Apps
The Windows Runtime uses the COM threading model. In this model,
objects are hosted in different apartments, depending on how they
handle their synchronization. Thread-safe objects are hosted in the
multi-threaded apartment (MTA). Objects that must be accessed by a
single thread are hosted in a single-threaded apartment (STA).
In an app that has a UI, the ASTA (Application STA) thread is
responsible for pumping window messages and is the only thread in the
process that can update the STA-hosted UI controls. This has two
consequences. First, to enable the app to remain responsive, all
CPU-intensive and I/O operations should not be run on the ASTA thread.
Second, results that come from background threads must be marshaled
back to the ASTA to update the UI. In a C++ Windows 8.x Store app,
MainPage and other XAML pages all run on the ATSA. Therefore, task
continuations that are declared on the ASTA are run there by default
so you can update controls directly in the continuation body. However,
if you nest a task in another task, any continuations on that nested
task run in the MTA. Therefore, you need to consider whether to
explicitly specify on what context these continuations run.
是的,为了消除警告,编写源代码的方式略有不同。
消除警告
如果我修改 MainPage::MainPage() 的源代码,而不是在 concurrency::create_task()
启动的工作线程中使用 CoreWindow::GetForCurrentThread();
提供的 Windows::UI::Core::CoreWindow ^
,我会得到来自 UI 线程本身的 Dispatcher
然后在工作线程中使用 Dispatcher
对象,我不再收到警告。这是因为 Windows::UI::Core::CoreWindow
不是敏捷的,所以必须考虑 CoreWindow
对象来自的线程。但是 Dispatcher
对象是敏捷的。
编译器警告与从工作线程中通过 non-Agile 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有备注如下:
If you are on a worker thread and want to schedule work on the UI
thread, use CoreDispatcher::RunAsync
. Always set the priority to
CoreDispatcherPriority::Normal
or CoreDispatcherPriority::Low
, and
ensure that any chained callbacks also use
CoreDispatcherPriority::Normal
or CoreDispatcherPriority::Low
.
线程、异步和敏捷方法的背景知识
许多 .NET 功能和 Windows 运行时功能以及越来越多的通用功能都以 COM 控件和功能的形式提供。使用 COM 技术允许多种语言、平台和技术使用相同的功能。
然而,伴随 COM 技术而来的是大量的复杂性,幸运的是,通过使用各种语言特定的包装器将其封装起来,可以在很大程度上隐藏它。
COM 技术的一个考虑是公寓的想法。 MSDN article Processes, Threads, and Apartments 提供了对该主题的一些技术性介绍。
Creating Asynchronous Operations in C++ for Windows Store Apps
The Windows Runtime uses the COM threading model. In this model,
objects are hosted in different apartments, depending on how they
handle their synchronization. Thread-safe objects are hosted in the
multi-threaded apartment (MTA). Objects that must be accessed by a
single thread are hosted in a single-threaded apartment (STA).
In an app that has a UI, the ASTA (Application STA) thread is
responsible for pumping window messages and is the only thread in the
process that can update the STA-hosted UI controls. This has two
consequences. First, to enable the app to remain responsive, all
CPU-intensive and I/O operations should not be run on the ASTA thread.
Second, results that come from background threads must be marshaled
back to the ASTA to update the UI. In a C++ Windows 8.x Store app,
MainPage and other XAML pages all run on the ATSA. Therefore, task
continuations that are declared on the ASTA are run there by default
so you can update controls directly in the continuation body. However,
if you nest a task in another task, any continuations on that nested
task run in the MTA. Therefore, you need to consider whether to
explicitly specify on what context these continuations run.
随着 Windows 运行时,引入了敏捷和 non-Agile 线程的概念。 Microsoft Docs article Threading and Marshaling (C++/CX) 为 C++ 程序员提供了介绍。
In the vast majority of cases, instances of Windows Runtime classes,
like standard C++ objects, can be accessed from any thread. Such
classes are referred to as "agile". However, a small number of Windows
Runtime classes that ship with Windows are non-agile, and must be
consumed more like COM objects than standard C++ objects. You don't
need to be a COM expert to use non-agile classes, but you do need to
take into consideration the class's threading model and its marshaling
behavior. This article provides background and guidance for those rare
scenarios in which you need to consume an instance of a non-agile
class.
另见
Threading Model 讨论 WPF 线程模型。
Historically, Windows allows UI elements to be accessed only by the
thread that created them. This means that a background thread in
charge of some long-running task cannot update a text box when it is
finished. Windows does this to ensure the integrity of UI components.
A list box could look strange if its contents were updated by a
background thread during painting.
的很好的解释
我有一个使用 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) 的解释,其中提到了警告,"Compiler warning when consuming non-agile classes (C4451)" 但是我不确定我是否遇到实际问题。
是否有不同的、更可接受的方式从任务延续更新 UI?
我正在使用 DispatchedHandler()
以便从任务继续访问 UI 线程。如果我尝试使用 myTextBlock->Text = "this is my text and some more text after sleep";
而不将其包装在 DispatchedHandler()
中,我会得到一个异常。异常是可以理解的,因为 then
任务继续在 UI 线程中不再 运行ning。
这个 Whosebug,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 window 中看到了预期的消息系列。在几秒钟的过程中显示了一系列文本字符串,其中包括一系列以在第一个任务继续的循环中生成的递增值 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
另请参阅其他 Whosebug 帖子:
- Run code on UI thread in WinRT
- How do I determine if I need to dispatch to UI thread in WinRT/Metro?
MSDN article: Concurrency Runtime
Task Parallelism (Concurrency Runtime) 提供并发 运行 时间和各种选项的概述。几个示例和大量指向其他 material.
的链接来自编译器的这些警告并不意味着您做错了什么,而是意味着您可能做错了什么。
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 其中有以下注释:
This class is not agile, which means that you need to consider its threading model and marshaling behavior. For more info, see Threading and Marshaling (C++/CX).
Creating Asynchronous Operations in C++ for Windows Store Apps
The Windows Runtime uses the COM threading model. In this model, objects are hosted in different apartments, depending on how they handle their synchronization. Thread-safe objects are hosted in the multi-threaded apartment (MTA). Objects that must be accessed by a single thread are hosted in a single-threaded apartment (STA).
In an app that has a UI, the ASTA (Application STA) thread is responsible for pumping window messages and is the only thread in the process that can update the STA-hosted UI controls. This has two consequences. First, to enable the app to remain responsive, all CPU-intensive and I/O operations should not be run on the ASTA thread. Second, results that come from background threads must be marshaled back to the ASTA to update the UI. In a C++ Windows 8.x Store app, MainPage and other XAML pages all run on the ATSA. Therefore, task continuations that are declared on the ASTA are run there by default so you can update controls directly in the continuation body. However, if you nest a task in another task, any continuations on that nested task run in the MTA. Therefore, you need to consider whether to explicitly specify on what context these continuations run.
是的,为了消除警告,编写源代码的方式略有不同。
消除警告
如果我修改 MainPage::MainPage() 的源代码,而不是在 concurrency::create_task()
启动的工作线程中使用 CoreWindow::GetForCurrentThread();
提供的 Windows::UI::Core::CoreWindow ^
,我会得到来自 UI 线程本身的 Dispatcher
然后在工作线程中使用 Dispatcher
对象,我不再收到警告。这是因为 Windows::UI::Core::CoreWindow
不是敏捷的,所以必须考虑 CoreWindow
对象来自的线程。但是 Dispatcher
对象是敏捷的。
编译器警告与从工作线程中通过 non-Agile 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有备注如下:
If you are on a worker thread and want to schedule work on the UI thread, use
CoreDispatcher::RunAsync
. Always set the priority toCoreDispatcherPriority::Normal
orCoreDispatcherPriority::Low
, and ensure that any chained callbacks also useCoreDispatcherPriority::Normal
orCoreDispatcherPriority::Low
.
线程、异步和敏捷方法的背景知识
许多 .NET 功能和 Windows 运行时功能以及越来越多的通用功能都以 COM 控件和功能的形式提供。使用 COM 技术允许多种语言、平台和技术使用相同的功能。
然而,伴随 COM 技术而来的是大量的复杂性,幸运的是,通过使用各种语言特定的包装器将其封装起来,可以在很大程度上隐藏它。
COM 技术的一个考虑是公寓的想法。 MSDN article Processes, Threads, and Apartments 提供了对该主题的一些技术性介绍。
Creating Asynchronous Operations in C++ for Windows Store Apps
The Windows Runtime uses the COM threading model. In this model, objects are hosted in different apartments, depending on how they handle their synchronization. Thread-safe objects are hosted in the multi-threaded apartment (MTA). Objects that must be accessed by a single thread are hosted in a single-threaded apartment (STA).
In an app that has a UI, the ASTA (Application STA) thread is responsible for pumping window messages and is the only thread in the process that can update the STA-hosted UI controls. This has two consequences. First, to enable the app to remain responsive, all CPU-intensive and I/O operations should not be run on the ASTA thread. Second, results that come from background threads must be marshaled back to the ASTA to update the UI. In a C++ Windows 8.x Store app, MainPage and other XAML pages all run on the ATSA. Therefore, task continuations that are declared on the ASTA are run there by default so you can update controls directly in the continuation body. However, if you nest a task in another task, any continuations on that nested task run in the MTA. Therefore, you need to consider whether to explicitly specify on what context these continuations run.
随着 Windows 运行时,引入了敏捷和 non-Agile 线程的概念。 Microsoft Docs article Threading and Marshaling (C++/CX) 为 C++ 程序员提供了介绍。
In the vast majority of cases, instances of Windows Runtime classes, like standard C++ objects, can be accessed from any thread. Such classes are referred to as "agile". However, a small number of Windows Runtime classes that ship with Windows are non-agile, and must be consumed more like COM objects than standard C++ objects. You don't need to be a COM expert to use non-agile classes, but you do need to take into consideration the class's threading model and its marshaling behavior. This article provides background and guidance for those rare scenarios in which you need to consume an instance of a non-agile class.
另见
Threading Model 讨论 WPF 线程模型。
的很好的解释Historically, Windows allows UI elements to be accessed only by the thread that created them. This means that a background thread in charge of some long-running task cannot update a text box when it is finished. Windows does this to ensure the integrity of UI components. A list box could look strange if its contents were updated by a background thread during painting.