延续链中是否需要共享指针?

Are shared pointers necessary in continuation chains?

我有一个使用 lambda 表达式的延续链,其中一个任务分配给一个变量,下一个任务从该变量读取。 Microsoft suggests using a shared_ptr to wrap the variable even when the variable is a reference-counted handle (^). 当 lambda 表达式按值捕获时,引用计数句柄不会增加其引用计数吗?那么为什么有必要用 shared_ptr 包装一个引用计数句柄?

文档清楚地表明他们关注的案例是

one task in a continuation chain assigns to a variable, and another task reads that variable

(重点是我的。)这不是对象生命周期的问题,而是对象身份的问题。

Hilo project 为例,密切注意 decoder 变量(这是一个 shared_ptr<BitmapDecoder^>):

task<InMemoryRandomAccessStream^> ThumbnailGenerator::CreateThumbnailFromPictureFileAsync(
    StorageFile^ sourceFile, 
    unsigned int thumbSize)
{
    (void)thumbSize; // Unused parameter
    auto decoder = make_shared<BitmapDecoder^>(nullptr);
    auto pixelProvider = make_shared<PixelDataProvider^>(nullptr);
    auto resizedImageStream = ref new InMemoryRandomAccessStream();
    auto createThumbnail = create_task(
        sourceFile->GetThumbnailAsync(
        ThumbnailMode::PicturesView, 
        ThumbnailSize));

    return createThumbnail.then([](StorageItemThumbnail^ thumbnail)
    {
        IRandomAccessStream^ imageFileStream = 
            static_cast<IRandomAccessStream^>(thumbnail);

        return BitmapDecoder::CreateAsync(imageFileStream);

    }).then([decoder](BitmapDecoder^ createdDecoder)
    {
        (*decoder) = createdDecoder;
        return createdDecoder->GetPixelDataAsync( 
            BitmapPixelFormat::Rgba8,
            BitmapAlphaMode::Straight,
            ref new BitmapTransform(),
            ExifOrientationMode::IgnoreExifOrientation,
            ColorManagementMode::ColorManageToSRgb);

    }).then([pixelProvider, resizedImageStream](PixelDataProvider^ provider)
    {
        (*pixelProvider) = provider;
        return BitmapEncoder::CreateAsync(
            BitmapEncoder::JpegEncoderId, 
            resizedImageStream);

    }).then([pixelProvider, decoder](BitmapEncoder^ createdEncoder)
    {
        createdEncoder->SetPixelData(BitmapPixelFormat::Rgba8,
            BitmapAlphaMode::Straight,
            (*decoder)->PixelWidth,
            (*decoder)->PixelHeight,
            (*decoder)->DpiX,
            (*decoder)->DpiY,
            (*pixelProvider)->DetachPixelData());
        return createdEncoder->FlushAsync();

    }).then([resizedImageStream]
    {
        resizedImageStream->Seek(0);
        return resizedImageStream;
    });
}

decoder 变量首先在延续之外定义,因为在多个延续中需要它。此时,它的值为空。它是在第二个延续中获取和设置的,并且该对象的属性(PixelWidth 等)在第四个延续中使用。

如果您改为将 decoder 定义为 BitmapDecoder^,将其设置为 nullptr,然后在第二个延续中为其赋值,则该更改不会传播到后续的延续,因为更改无法反映回初始句柄(lambda 已经复制了句柄,实质上是复制了内存地址 0x00000000)。

为了更新原始版本(和后续参考),您需要一个额外的间接寻址(例如 BitmapDecoder^*)。 shared_ptr<BitmapDecoder^> 就是这样一种间接寻址,它很有用,因为与原始指针不同,您不需要管理指针的生命周期,这就是文档中推荐使用它的原因。

在其他情况下捕获一个 Object^ 就足够了,例如,如果我在我的延续之外创建了一个 TextBlock^ 并在第一个延续中设置它的一些属性并阅读其他一些在随后的延续中的属性。在这种情况下,所有句柄都引用相同的底层对象,并且没有继续试图覆盖对象本身的标识。 (但是,正如最初提到的,这不是文档所指的用例。)