Using C++/WinRT’s final_release to control which thread destructs your object

Raymond Chen

It is often that case that an object is intended to be used only from a single thread, particularly if it is tied to the user interface somehow, since user interface objects are generally single-threaded. On the other hand, it may have to subscribe to events that are raised from background threads.

namespace winrt
{
    using namespace Windows::System::Power;
}

struct MyViewModel : MyViewModelT<MyViewModel>
{
    ...

    winrt::fire_and_forget OnEnergySaverStatusChanged(
        winrt::IInspectable const&, winrt::IInspectable const&)
    {
        auto lifetime = get_strong();
        co_await winrt::resume_foreground(Dispatcher());
        m_isEnergySaverOn = (winrt::PowerManager::EnergySaverStatus()
                              == winrt::EnergySaverStatus::On);
        RaiseProperyChangeEvent(L"IsEnergySaverOn");
    }

    winrt::EnergySaverStatusChanged_revoker m_energySaverStatusChangedToken =
        winrt::PowerManager::EnergySaverStatusChanged(
            { get_weak(), &MyViewModel::OnEnergySaverStatusChanged });
};

In the above example, we have a view model that tracks the system Energy Saver status. The change notification for this is raised on a background thread, so we need to switch to the foreground thread before doing our calculations and raising the property-change event.

This pattern is common for many types of notifications: Receive the notification on a background thread and immediately switch to the UI thread to process it. Doing all the work on the UI thread avoids race conditions.

But there’s a small problem here: There’s a race condition if the foreground thread completes its work before the background thread releases its temporary strong reference.

To make the weak and strong references more explicit, let me rewrite the code this way:

struct MyViewModel : MyViewModelT<MyViewModel>
{
    ...

    winrt::EnergySaverStatusChanged_revoker m_energySaverStatusChangedToken =
        winrt::PowerManager::EnergySaverStatusChanged(
            [weak = get_weak()](auto&&, auto&&)
            -> winrt::fire_and_forget {
                if (auto strong = weak.get()) {
                    strong->Dispatcher().RunAsync([strong] {
                        m_isEnergySaverOn = (winrt::PowerManager::EnergySaverStatus()
                                              == winrt::EnergySaverStatus::On);
                        RaiseProperyChangeEvent(L"IsEnergySaverOn");
                    });
                }
            });
};

Now we can follow the reference counts. Initially the view model’s reference count is 1 because the view has a reference to it.

UI thread Background thread Reference count
  EnergySaverStatusChanged 1
  get strong reference 2
  capture strong reference into lambda 3
View tears down    
View model released   2
  RunAsync  
update m_isEnergySaverOn    
raise property change event    
destruct strong reference inside lambda   1
  destruct strong reference 0

In this unfortunate sequence of events, the last reference is released on the background thread, and the view model therefore runs its destructor on the background thread, which is bad news because the view model really is a single-threaded object, and its destructor is going to assume that it is running on the UI thread. The reference on the background thread exists for the sole purpose of getting control back to the UI thread, but it ends up being the one holding the bag when everything cleans up.

The fix here is to use final_release to get control back to the UI thread for the purpose of destructing there.

struct MyViewModel : MyViewModelT<MyViewModel>
{
    ...

    winrt::fire_and_forget final_release(std::unique_ptr<MyViewModel> self)
    {
        co_await winrt::resume_foreground(self->Dispatcher());
        // destruct the object on the UI thread
    }

    ...
};

If your implementation class has a method called final_release, it will be called with a std::unique_ptr holding the object that is about to be destructed. This gives you a chance to do something just before destruction, and a common reason for doing this is to move the unique_ptr to the UI thread, so that when it destructs, the underlying object destructs on the UI thread.

2 comments

Discussion is closed. Login to edit/delete existing comments.

  • Jacob Manaker 0

    At first sight, the problem really has nothing to do with event handling. Any WinRT code that assumes a specific thread calls the destructor is broken, because you have no control over reference counts.

    One could equally well posit a collection type `FancyCollection` with a cache that updates asynchronously on another thread and a custom `GridView` that uses a `FancyCollection` to store `ViewModel`s. You pass your `ViewModel` to that custom `GridView`; then the cache thread updates and stores your `ViewModel` in the cache. Eventually, the view gets torn down. Finally, the cache updates and releases your `ViewModel` on the cache thread. In that scenario, everything works by design, but you’re still getting destructed on a private thread that you have no access to and didn’t even know existed.

    OTOH, you can superficially control destructor threading by putting an object inside an STA. I guess your point is that the STA doesn’t protect against multithreading “calls from inside the house”: because the WinRT async implementation does not coordinate with COM apartments, any thread you instantiate (via, say, `resume_foreground`) can call the destructor, and you need to protect against that even in an STA?

    • Raymond ChenMicrosoft employee 0

      The point of final_release is to let you have control between the time the reference count goes to zero and the execution of the destructor. When the cache releases the ViewModel on the cache thread, C++/WinRT calls your final_release to say “Normally, I would just destruct this object right away, but instead, I’m going to give you the last reference to the object (in the form of a unique_ptr), and you can manage its destruction yourself.” So everybody is happy. The cache can release the object on any thread it likes, and the ViewModel can move the object to the UI thread to run the destructor.

Feedback usabilla icon