1

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:

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.

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
  • Your first comment block is incorrect - - it's running on a threadpool thread. Also you shouldn't `Sleep(2000)` at the beginning of the function since that really is on the UI thread. The compiler is warning you that if you use the `CoreWindow` from the wrong thread it will fail. The `Dispatcher` usage ensures that you don't do that. – Peter Torr - MSFT Apr 05 '18 at 16:21
  • @PeterTorr-MSFT Sorry but I really don't find your comment very helpful about my actual questions. I made changes regarding comment block and moved the `Sleep()` however I still would like some resource with better information about accessing the UI from another thread. – Richard Chambers Apr 05 '18 at 16:36
  • My last two sentences explain why you get the warning and why your code works. The `Dispatcher->RunAsync` call ensures the work is done on the appropriate thread. You can read more about [Marshalling on Wikipedia](https://en.wikipedia.org/wiki/Marshalling_(computer_science)) or [COM threading](https://en.wikipedia.org/wiki/Component_Object_Model#Threading). – Peter Torr - MSFT Apr 05 '18 at 16:41
  • The difference in your second example is that you're passing the `CoreDispatcher` (which is agile) to the lambda, but in the first example you pass the entire `CoreWindow` (which is not agile). – Peter Torr - MSFT Apr 08 '18 at 18:10
  • @PeterTorr-MSFT thank you for the confirmation. I thought that might be the case and was going to do some additional research about agile and non-agile. I got a bit side tracked doing the research for an answer to this posted question, https://stackoverflow.com/questions/49642684/which-process-is-best-to-implement-continuous-work-between-thread-and-task/49695288 which appears somewhat relevant. Not the question so much as the research on Task specifically. – Richard Chambers Apr 08 '18 at 20:26

1 Answers1

1

These warnings from the compiler do not mean that you are doing something wrong, it means that you may be doing something wrong.

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 which has the following note:

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.

And yes, there is a slightly different way to write the source so as to eliminate the warning.

Eliminating the Warnings

If I modify the source of MainPage::MainPage() so that rather than use the Windows::UI::Core::CoreWindow ^ provided by CoreWindow::GetForCurrentThread(); within the worker thread started by concurrency::create_task() I instead get the Dispatcher from the UI thread itself then use the Dispatcher object in the worker thread, I no longer get the warning. This is because Windows::UI::Core::CoreWindow is not Agile so the thread that the CoreWindow object comes from must be a consideration. However the Dispatcher object is Agile.

The compiler warning had to do with accessing the Dispatcher of the UI thread through the non-Agile CoreWindow object from within the worker thread whereas with this version of getting the reference to the UI thread dispatcher within the UI thread and then using that Dispatcher reference, the compiler is fine with that.

This version of the source code looks like:

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 has Remarks as follows:

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.

Background on Threading and Async and Agile Methods

Much of the .NET functionality and Windows Runtime functionality as well as more and more general purpose functionality is being provided in the form of COM controls and functionality. Using the COM technology allows for the same functionality to be used by a wide variety of languages, platforms, and technologies.

However along with COM technology is a great deal of complexity which fortunately can be hidden to a great extent by encapsulating it with various language specific wrappers.

One consideration of COM technology is the idea of Apartments. The MSDN article Processes, Threads, and Apartments provides a somewhat technical introduction to the subject.

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.

With the Windows Runtime the concept of Agile and non-Agile threads was introduced. The Microsoft Docs article Threading and Marshaling (C++/CX) provides an introduction for the C++ programmer.

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.

See also

Threading Model which discusses WPF threading models.

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.

A nice explanation on COM Threading Models from The Open Group

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106