windows C++-并发和异步操作(下)

异步返回 Windows 运行时类型

在下一个示例中,我们将针对特定 URI 封装对 RetrieveFeedAsync 的调用,以为我们提供异步返回 SyndicationFeed 的 RetrieveBlogFeedAsync 函数。

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void PrintFeed(SyndicationFeed const& syndicationFeed)
{
    for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
    {
        std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
    }
}

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> RetrieveBlogFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;
    return syndicationClient.RetrieveFeedAsync(rssFeedUri);
}

int main()
{
    winrt::init_apartment();

    auto feedOp{ RetrieveBlogFeedAsync() };
    // do other work.
    PrintFeed(feedOp.get());
}

在上述示例中,RetrieveBlogFeedAsync 返回 IAsyncOperationWithProgress,其具有进度值和返回值。 我们可以在 RetrieveBlogFeedAsync 执行其操作并检索提要的同时进行其他工作。 然后,在该异步操作对象上调用 get,以阻塞、等待其完成,然后获取该操作的结果。

如果要异步返回 Windows 运行时类型,则应返回 IAsyncOperation<TResult> 或 IAsyncOperationWithProgress<TResult, TProgress>。 任何第一方或第三方运行时类或可以传入/传出 Windows 运行时函数的任何类型(例如 int 或 winrt::hstring)都符合条件。 如果尝试对非 Windows 运行时类型使用其中一种异步操作类型,编译器可帮助你处理“T 必须为 WinRT 类型”错误。

如果协同例程没有至少一条 co_await 语句,则为了符合成为协同例程的资格,它必须至少有一条 co_return 或一条 co_yield 语句。 在某些情况下,协同例程可以返回值而不引入任何异步,因此不阻塞也不切换上下文。 下面是一个通过缓存值来实现上述功能(第二次及后续调用时)的示例。

winrt::hstring m_cache;

IAsyncOperation<winrt::hstring> ReadAsync()
{
    if (m_cache.empty())
    {
        // Asynchronously download and cache the string.
    }
    co_return m_cache;
}
异步返回非 Windows 运行时类型

如果要异步返回非 Windows 运行时类型的类型,则应返回并行模式库 (PPL) concurrency::task。 建议使用 concurrency::task,因为它将提供比 std::future 更好的性能(以及更好的兼容性)。

如果包含 <pplawait.h>,则可以使用 concurrency::task 作为协同例程类型。

// main.cpp
#include <iostream>
#include <ppltasks.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

concurrency::task<std::wstring> RetrieveFirstTitleAsync()
{
    return concurrency::create_task([]
        {
            Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
            SyndicationClient syndicationClient;
            SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
            return std::wstring{ syndicationFeed.Items().GetAt(0).Title().Text() };
        });
}

int main()
{
    winrt::init_apartment();

    auto firstTitleOp{ RetrieveFirstTitleAsync() };
    // Do other work here.
    std::wcout << firstTitleOp.get() << std::endl;
}
参数传递

对于同步函数,默认情况下应该使用 const& 参数。 这将避免复制开销(涉及引用计数,意味着互锁的增加和减少)。

// Synchronous function.
void DoWork(Param const& value);

// 但如果向协同例程传递引用参数,可能会遇到问题。

// NOT the recommended way to pass a value to a coroutine!
IASyncAction DoWorkAsync(Param const& value)
{
    // While it's ok to access value here...

    co_await DoOtherWorkAsync(); // (this is the first suspension point)...

    // ...accessing value here carries no guarantees of safety.
}

在协同程序中,在第一个暂停点之前,执行是同步的;到达第一个暂停点时,控制返回到调用方,调用帧超出范围。 在协同例程恢复时,引用参数引用的源值可能已发生更改。 从协同例程的角度来看,引用参数具有不受控制的生命周期。 因此,在上面的示例中,在 co_await 之前,我们可以安全地访问 value,但之后就无法保证安全了。 如果调用方销毁了 value,则尝试在协同例程中访问它会导致内存损坏。 如果 DoOtherWorkAsync 函数有可能暂停并在恢复后尝试使用 value,我们也无法安全地将 value 传递给 DoOtherWorkAsync。

为了能够在暂停和恢复后安全地使用参数,默认情况下,协同例程应使用按值传递,以确保按值进行捕获并避免生命周期问题。 确信不遵从该指引也能安全进行操作的情况是很少见的。

// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&

按值传递要求参数的移动或复制开销不高,智能指针通常就是这样的。

传递 const 值是否是一个好的做法也还存在争议(除非你想移动值)。 它不会对要复制的源值产生任何影响,但有助于表明意图,并避免你无意间修改副本。

// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);

如果不能更改协同例程的签名,但是能够更改实现,则可在首次执行 co_await 之前进行本地复制。 

IASyncAction DoWorkAsync(Param const& value)
{
    auto safe_value = value;
    // It's ok to access both safe_value and value here.

    co_await DoOtherWorkAsync();

    // It's ok to access only safe_value here (not value).
}

如果 Param 复制起来开销很大,则在首次执行 co_await 之前只提取所需的片段。 

IASyncAction DoWorkAsync(Param const& value)
{
    auto safe_data = value.data;
    // It's ok to access safe_data, value.data, and value here.

    co_await DoOtherWorkAsync();

    // It's ok to access only safe_data here (not value.data, nor value).
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值