windows C++-高级并发和异步(二)

 协同例程中的执行上下文、恢复和切换(下)

在上部分的方案中,调用 StorageFile::OpenAsync 会使效率略微下降。 恢复时有必要将上下文切换到后台线程(这样,处理程序便可以将执行返回给调用方),然后 C++/WinRT 会还原 UI 线程上下文。 但是,在此情况下,在我们即将更新 UI 之前,没有必要处于 UI 线程中。 在调用 winrt::resume_background 之前调用的 Windows 运行时 API 越多,发生的不必要往返上下文切换也越多。 解决方法是在此之前不要调用任何 Windows 运行时 API。 将所有此类调用移到 winrt::resume_background 的后面。

IAsyncAction MainPage::ClickHandler(IInspectable /* sender */, RoutedEventArgs /* args */)
{
    // We begin in the UI context.

    co_await winrt::resume_background();

    // We're now on a background thread.

    // Call StorageFile::OpenAsync to load an image file.

    // Process the image.

    co_await winrt::resume_foreground(this->Dispatcher());

    // We're back on MainPage's UI thread.

    // Display the image in the UI.
}

如果需要提前执行某些调用,可以编写自己的 await 适配器。 例如,若要运行 co_await 以便在完成异步操作所在的同一线程上进行恢复(因此不会发生上下文切换),可以先编写如下所示的 await 适配器。

以下代码示例仅用于培训目的,可帮助你开始了解 await 适配器的工作原理。 若要在自己的基代码中使用此方法,我们建议你开发并测试自己的 await 适配器结构。 例如,可以编写 complete_on_any、complete_on_current 和 complete_on(dispatcher) 。 另请考虑将这些结构设置为使用 IAsyncXxx 类型作为模板参数的模板。

struct no_switch
{
    no_switch(Windows::Foundation::IAsyncAction const& async) : m_async(async)
    {
    }

    bool await_ready() const
    {
        return m_async.Status() == Windows::Foundation::AsyncStatus::Completed;
    }

    void await_suspend(std::experimental::coroutine_handle<> handle) const
    {
        m_async.Completed([handle](Windows::Foundation::IAsyncAction const& /* asyncInfo */, Windows::Foundation::AsyncStatus const& /* asyncStatus */)
        {
            handle();
        });
    }

    auto await_resume() const
    {
        return m_async.GetResults();
    }

private:
    Windows::Foundation::IAsyncAction const& m_async;
};

若要了解如何使用 no_switch await 适配器,首先需要知道,当 C++ 编译器遇到 co_await 表达式时,它会查找名为 await_ready、await_suspend 和 await_resume 的函数。 C++/WinRT 库提供了这些函数,使你在默认情况下能够获得合理行为,如下所示。

IAsyncAction async{ ProcessFeedAsync() };
co_await async;

若要使用 no_switch await 适配器,只需将该 co_await 表达式的类型从 IAsyncXxx 更改为 no_switch,如下所示。 

IAsyncAction async{ ProcessFeedAsync() };
co_await static_cast<no_switch>(async);

然后,C++ 编译器不会查找与 IAsyncXxx 匹配的三个 await_xxx 函数,而是查找与 no_switch 匹配的函数。

深入了解 winrt::resume_foreground(上)

从 C++/WinRT 2.0 开始,winrt::resume_foreground 函数会暂停,即使是从调度程序线程来调用它(在以前的版本中,它可能会在某些情况下引入死锁,因为它暂停的前提是尚未位于调度程序线程上)。

当前的行为意味着,你可以依赖于堆栈展开和进行的重新排队;这对于系统稳定很重要,尤其是在低级别系统代码中。 在上面的编程时仔细考虑线程相关性部分列出的最后的代码演示了如何在后台线程上执行某些复杂的计算,然后切换到相应的 UI 线程,以便更新用户界面 (UI)。

下面是 winrt::resume_foreground 的具体代码。

auto resume_foreground(...) noexcept
{
    struct awaitable
    {
        bool await_ready() const
        {
            return false; // Queue without waiting.
            // return m_dispatcher.HasThreadAccess(); // The C++/WinRT 1.0 implementation.
        }
        void await_resume() const {}
        void await_suspend(coroutine_handle<> handle) const { ... }
    };
    return awaitable{ ... };
};

这种当前行为与过去行为的对比类似于 Win32 应用程序开发过程中出现的 PostMessage 和 SendMessage 之间的差异。 PostMessage 先将工作排队,然后在不等待工作完成的情况下展开堆栈。 堆栈展开有时候很重要。

winrt::resume_foreground 函数一开始也只支持在 Windows 10 之前引入的 CoreDispatcher(绑定到 CoreWindow)。 自那以后,我们引入了更灵活高效的调度程序:DispatcherQueue。 你可以创建适合自己使用的 DispatcherQueue。 让我们考虑一下这个简单的控制台应用程序。

using namespace Windows::System;

winrt::fire_and_forget RunAsync(DispatcherQueue queue);
 
int main()
{
    auto controller{ DispatcherQueueController::CreateOnDedicatedThread() };
    RunAsync(controller.DispatcherQueue());
    getchar();
}

上面的示例在专用线程上创建一个队列(包含在控制器中),然后将控制器传递给协同程序。 协同程序可以使用队列在专用线程上等待(先暂停再继续)。 DispatcherQueue 的另一常见用法是在传统桌面或 Win32 应用的当前 UI 线程上创建一个队列。

DispatcherQueueController CreateDispatcherQueueController()
{
    DispatcherQueueOptions options
    {
        sizeof(DispatcherQueueOptions),
        DQTYPE_THREAD_CURRENT,
        DQTAT_COM_STA
    };
 
    ABI::Windows::System::IDispatcherQueueController* ptr{};
    winrt::check_hresult(CreateDispatcherQueueController(options, &ptr));
    return { ptr, take_ownership_from_abi };
}

上面的代码演示了如何调用 Win32 函数并将其纳入 C++/WinRT 项目中,方法是:直接调用 Win32 样式的 CreateDispatcherQueueController 函数来创建控制器,然后将生成的队列控制器的所有权以 WinRT 对象形式移交给调用方。 这也正是你能够在现有的 Petzold 样式 Win32 桌面应用程序上为高效无缝排队提供支持的方式。 

winrt::fire_and_forget RunAsync(DispatcherQueue queue);
 
int main()
{
    Window window;
    auto controller{ CreateDispatcherQueueController() };
    RunAsync(controller.DispatcherQueue());
    MSG message;
 
    while (GetMessage(&message, nullptr, 0, 0))
    {
        DispatchMessage(&message);
    }
}

上面这个简单的 main 函数一开始就创建一个窗口。 你可以想象一下,这样会注册一个 window 类,然后调用 CreateWindow 来创建顶级桌面窗口。 然后调用 CreateDispatcherQueueController 函数来创建队列控制器,再通过该控制器拥有的调度程序队列调用某个协同程序。 然后进入传统的消息泵,在其中的此线程上自然而然地恢复协同程序。 然后,你可以回到协同程序所在的位置,在应用程序中完成异步的或基于消息的工作流。 

winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
    ... // Begin on the calling thread...
 
    co_await winrt::resume_foreground(queue);
 
    ... // ...resume on the dispatcher thread.
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值