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

并发和异步的由来已经很久了,对于从xp开始编程的人来说,这个概念并不陌生,但问题在于,在早期,这两个技术被认为是操作系统提供的服务,而非编程语言的概念。

事情发生变化的原因,和C++标准不断变迁的原因类似,编程语言的演化是更加倾向于人类语言而不是倾向于机器语言,当开发的效率压过运行的效率的时候,这种编程语言越做越大就更加的明显了。但需要注意黑体字的前提,因为在许多特殊行业中,运行的效率是压过开发的效率的

编程语言的演化是两个方向的,要么越来越接近底层,要么越来越接近上层。

将工作卸载到 Windows 线程池

协同例程与任何其他函数的类似之处在于,调用方将会阻塞到某个函数向其返回了执行为止。 另外,协同例程返回的第一个机会是第一个 co_await、co_return 或 co_yield。

因此,在协同例程中执行受计算限制的工作之前,需要将执行返回给调用方(换句话说,引入暂停点),使调用方不被阻塞。 如果还没有对其他某个操作运行 co_await 来做到这一点,则可以对 winrt::resume_background 函数运行 co_await。 这会将控制权返回给调用方,然后立即在某个线程池线程上恢复执行。

实现中使用的线程池是底层 Windows 线程池,因此具有极高的效率。

IAsyncOperation<uint32_t> DoWorkOnThreadPoolAsync()
{
    co_await winrt::resume_background(); // Return control; resume on thread pool.

    uint32_t result;
    for (uint32_t y = 0; y < height; ++y)
    for (uint32_t x = 0; x < width; ++x)
    {
        // Do compute-bound work here.
    }
    co_return result;
}
编程时仔细考虑线程相关性

该方案继续对上一个方案进行扩展。 你已将一些工作卸载到线程池,但希望在用户界面 (UI) 中显示进度。 

IAsyncAction DoWorkAsync(TextBlock textblock)
{
    co_await winrt::resume_background();
    // Do compute-bound work here.

    textblock.Text(L"Done!"); // Error: TextBlock has thread affinity.
}

上述代码抛出一个 winrt::hresult_wrong_thread 异常,因为必须从创建 TextBlock 的线程(即 UI 线程)更新 TextBlock。 一种解决方案是捕获最初调用协同例程的线程上下文。 为此,请实例化 winrt::apartment_context 对象,执行后台工作,然后对 apartment_context 运行 co_await 以切回到调用上下文。 

IAsyncAction DoWorkAsync(TextBlock textblock)
{
    winrt::apartment_context ui_thread; // Capture calling context.

    co_await winrt::resume_background();
    // Do compute-bound work here.

    co_await ui_thread; // Switch back to calling context.

    textblock.Text(L"Done!"); // Ok if we really were called from the UI thread.
}

只要上面的协同例程是从创建 TextBlock 的 UI 线程调用的,这种方法是可行的。 在应用中,有很多时候都是可以保证这一点的。

若要通过某种更通用的解决方案来更新 UI(包括不确定调用线程的情况)可以对 winrt::resume_foreground 函数运行 co_await,以切换到特定的前台线程。 在以下代码示例中,我们通过传递与 TextBlock 关联的调度程序对象(通过访问其 Dispatcher 属性)来指定前台线程。 winrt::resume_foreground 实现对该调度程序对象调用 CoreDispatcher.RunAsync,以执行协同例程中该调度程序对象之后的工作。

IAsyncAction DoWorkAsync(TextBlock textblock)
{
    co_await winrt::resume_background();
    // Do compute-bound work here.

    // Switch to the foreground thread associated with textblock.
    co_await winrt::resume_foreground(textblock.Dispatcher());

    textblock.Text(L"Done!"); // Guaranteed to work.
}

winrt::resume_foreground 函数采用可选的优先级参数。 如果使用该参数,则可以使用上面所示的模式。 如果不使用,则可以选择将 co_await winrt::resume_foreground(someDispatcherObject); 简化为 co_await someDispatcherObject;。 

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

概括地说,在协同例程中某个暂停点之后,原始执行线程可能会消失,而恢复可能会在任何线程上发生(换而言之,任何线程都可以针对异步操作调用 Completed 方法)。

但是,如果对四个 Windows 运行时异步操作类型 (IAsyncXxx) 中的任何一个运行 co_await,则 C++/WinRT 会在运行 co_await 时捕获调用上下文。 另外,它可以当延续操作恢复时,你仍处于该上下文中。 为此,C++/WinRT 会检查你是否已进入调用上下文,如果没有,则切换到该上下文。 如果在运行 co_await 之前你处于单线程单元 (STA) 线程中,则运行之后你仍处于相同的线程中;如果在运行 co_await 之前你处于多线程单元 (MTA) 线程中,则运行之后你将处于不同的线程中。

IAsyncAction ProcessFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;

    // The thread context at this point is captured...
    SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
    // ...and is restored at this point.
}

可以依赖此行为的原因在于,C++/WinRT 提供相应的代码,使这些 Windows 运行时异步操作类型能够适应 C++ 协同例程语言支持(这些代码片段称为等待适配器)。 C++/WinRT 中剩余的可等待类型只是一些线程池包装器和/或帮助器;因此它们会在线程池中完成。 

using namespace std::chrono_literals;
IAsyncOperation<int> return_123_after_5s()
{
    // No matter what the thread context is at this point...
    co_await 5s;
    // ...we're on the thread pool at this point.
    co_return 123;
}

 如果对其他某个类型运行 co_await,即使是在 C++/WinRT 协同例程实现中,则另一个库会提供适配器,你需要了解这些适配器在恢复和上下文方面的作用。

为了尽量减少上下文切换次数,可以使用本主题所述的某些方法。 让我们看看该操作的几个图示。 以下伪代码示例演示了一个事件处理程序的大纲。该处理程序调用 Windows 运行时 API 来加载图像,切换到后台线程来处理该图像,然后返回到 UI 线程以在 UI 中显示该图像。

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

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

    // The call to OpenAsync occurred on a background thread, but C++/WinRT has restored us to the UI thread by this point.

    co_await winrt::resume_background();

    // We're now on a background thread.

    // Process the image.

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

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

    // Display the image in the UI.
}

这会带来额外的问题,在下一部分讲述。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值