I have a sample UWP app with C++ and Visual Studio 2017 Community Edition that I am working to understand the PPL functionality.
I generated a UWP app then made the modifications below to the MainPage.xaml.cpp file. The purpose of these changes is to emulate an asynchronous operation that is taking multiple seconds and to update the displayed UI as the operation completes the various stages.
This works and the UI is updated.
However I do see the following warnings when I compile.
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
What do these warnings mean?
I did find this explanation of Threading and Marshaling (C++/CX) which mentions the warning, "Compiler warning when consuming non-agile classes (C4451)" however I am not sure if I have an actual problem or not.
Is there a different, more acceptable way to update the UI from a task continuation?
I am using DispatchedHandler()
in order to gain access to the UI thread from the task continuation. If I try to use myTextBlock->Text = "this is my text and some more text after sleep";
without wrapping it in a DispatchedHandler()
I get an exception. The exception is understandable since the then
task continuation is no longer running in the UI thread.
This stackoverflow, Warning C4451: Usage of ref class BackgroundTaskDeferral can lead to invalid marshaling indicates that using Platform:Agile
did resolve their warning.
However there is no explanation about what the warning actually means
The initial task creation does nothing other than to start the thread which is handling the asynchronous operation. Each of the then
continuation clauses does a Sleep()
to represent some action that takes time followed by updating the displayed UI screen with a message.
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
}
Additional Exploration #1
With the following changed MainPage::MainPage()
I am seeing the expected series of messages displayed in the UI window. Displayed over the course of several seconds are a series of text strings including a series of strings beginning with the incrementing value of iCount
generated in the loop in the first task continuation.
It appears that if the for (int iCount = 0; iCount < 3; iCount++) {
is placed within the new DispatchedHandler()
lambda it causes the UI thread to block for a number of seconds and the UI to become unresponsive and then the text string of the second task continuation is displayed and the UI becomes responsive again. If the for
is outside as in this source code sample, the UI thread is not blocked and the UI remains responsive.
Does this mean that the lambda contained within the new DispatchedHandler()
is handed over to the UI thread to run?
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";
}));
});
}
Additional notes
MVVM and Accessing the UI Thread in Windows Store Apps
Running WPF Application with Multiple UI Threads
See also other stackoverflow posts:
- 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) provides an overview of the concurrency runtime and various options. Several examples and lots of links to additional material.