Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

07 Dec 2019

C++/WinRT学习笔记(五):异步操作和强弱引用

C++/Winrt学习笔记

Concurrency and asynchronous operations with C++/WinRT

超过50毫秒的操作,WinRT一般都会将其设为异步操作。异步操作意味着操作调用者不必阻塞并等待操作完成(但是可以选择阻塞)。

WinRT定义了四种基本的异步操作

  • IAsyncAction,
  • IAsyncActionWithProgress,
  • IAsyncOperation, and
  • IAsyncOperationWithProgress<TResult, TProgress>.

IAsyncOperation和IAsyncOperationWithProgress<TResult, TProgress>中的类型参数只支持使用WinRT类型,否则会出现must be WinRT type"错误。

如果想要使用非WinRT类型,那么默认不支持从routine,但是可以使用传统的回调方式,比如concurrency::task(比std::future性能高一些)。

Coroutine的入口最好传WinRT类型的值,传值能够增加到WinRT类型实例的引用,保证该值的生命周期。传引用的话,当coroutine挂起的时候,其他执行过程有可能释放该值的实例,导致coroutine中该值引用无效的实例。

如果非要传引用,一个变通的办法是在挂起之前对传入的引用指向的值进行拷贝。

Standard arrays and vectors有说明如何传递一个标准的容器到异步函数中。

如果coroutine是类成员,那么需要保证this指针不被释放,参考 Strong and weak references in C++/WinRT

Strong and weak references in C++/WinRT

WinRT是基于COM,然后两者都是通过引用计数的方式来管理实例。采用引用计数的话,就有所有权问题。谁拥有强引用,就代表谁对对象实例拥有所有权;谁拥有弱引用,则表示谁只有使用权。强引用和弱引用的区别是,弱引用有可能会变得无效,因为对象被释放了。

如果一个类的成员函数是异步的,那么这个成员在执行的时候隐式地保留了类对象的指针,也就是this指针。万一,在异步执行的过程中,类对象被释放,那么this指针就指向了无效的区域。为了避免这个问题, winrt::implements提供了get_strong()函数,让类成员函数可以获取类对象,也就是this指针的强引用,避免对象被提前释放:

IAsyncOperation<winrt::hstring> RetrieveValueAsync()
{
    auto strong_this{ get_strong() }; // Keep *this* alive.
    co_await 5s;
    co_return m_value;
}

强引用会阻止对象被释放,有时候这会成为一个问题。如果不想组织对象被释放,只是想检查一下对象是否可以用,则可以使用弱引用:implements::get_weak

IAsyncOperation<winrt::hstring> RetrieveValueAsync()
{
    auto weak_this{ get_weak() }; // Maybe keep *this* alive.

    co_await 5s;

    if (auto strong_this{ weak_this.get() })
    {
        co_return m_value;
    }
    else
    {
        co_return L"";
    }
}

另一个常见的跟生命周期相关的问题发生在事件处理器中。如果一个类的成员函数被注册为一个事件的处理器,那么就得注意在这个类对象生命周期结束时取消注册事件处理。但是有时候即便注册取消了,依然会有事件进来,导致之前注册的成员函数被调用,在对象销毁的情况下,这大概率会导致灾难,特别是当该成员函数访问了类的内部状态。

解决办法是使用以强引用或者弱引用为参数的lambda作为事件处理回调,而不是隐式传递this:

event_source.Event([this, strong_this { get_strong()}](auto&& ...)
{
    std::wcout << m_value.c_str() << std::endl;
});

或者

event_source.Event([strong_this { get_strong()}](auto&& ...)
{
    std::wcout << strong_this->m_value.c_str() << std::endl;
});

弱引用的例子:

event_source.Event([weak_this{ get_weak() }](auto&& ...)
{
    if (auto strong_this{ weak_this.get() })
    {
        std::wcout << strong_this->m_value.c_str() << std::endl;
    }
});

不使用lambda,直接注册成员函数为事件处理器:

event_source.Event({ get_strong(), &EventRecipient::OnEvent });

一个使用弱引用的例子:

winrt::Windows::UI::Xaml::Controls::SwapChainPanel m_swapChainPanel;
winrt::event_token m_compositionScaleChangedEventToken;

void RegisterEventHandler()
{
    m_compositionScaleChangedEventToken = m_swapChainPanel.CompositionScaleChanged([weak_this{ get_weak() }]
        (Windows::UI::Xaml::Controls::SwapChainPanel const& sender,
        Windows::Foundation::IInspectable const& object)
    {
        if (auto strong_this{ weak_this.get() })
        {
            strong_this->OnCompositionScaleChanged(sender, object);
        }
    });
}

void OnCompositionScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender,
    Windows::Foundation::IInspectable const& object)
{
    //
}

C++/WinRT通过winrt::implements默认为winrt类型提供弱引用支持,在IWeakReferenceSource被查询的时候启用。

例子:

Class c;
winrt::weak_ref<Class> weak{ c };

auto weak = winrt::make_weak(c);

可以选择关闭这项功能:

struct MyImplementation: implements<MyImplementation, IStringable, no_weak_ref>
{
    ...
}

或者

struct MyRuntimeClass: MyRuntimeClassT<MyRuntimeClass, no_weak_ref>
{
    ...
}

(本篇完)