C++ Function signature that returns a PPL task?

3.1k views Asked by At

I am a total noob when it comes to using PPL tasks within the C++ environment, so I am having a hard time to figure out what would be the C++ syntax of the following C# code:

private static async Task<RandomAccessStreamReference> GetImageStreamRef()
{
    return RandomAccessStreamReference.CreateFromStream(await GetImageStream());
}

private static async Task<IRandomAccessStream> GetImageStream()
{
    var stream = new InMemoryRandomAccessStream();
    var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
    encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, width, height, 96, 96, imageBytes);
    await encoder.FlushAsync();
    return stream;
}

This C# code was taken from the Windows Store reversi Microsoft sample code. The best I could get so far is this:

Concurrency::task<IRandomAccessStream^> GetImageStream()
{
    auto stream = ref new InMemoryRandomAccessStream();
    task<BitmapEncoder^>(BitmapEncoder::CreateAsync(BitmapEncoder::JpegEncoderId, Stream)).then([this, stream, width, height, imageBytes](BitmapEncoder^ encoder)
    {
        encoder->SetPixelData(BitmapPixelFormat::Rgba8, BitmapAlphaMode::Ignore, width, height, 96.0, 96.0, imageBytes);
        return encoder->FlushAsync();
    }).then([this, stream]()
    {
        return stream; //Does this even make sense?
    });
    //return stream; //Not sure if I should have this here?
}

But it generates the following compile error:

error C4716: 'GetImageStream' : must return a value

I understand why this error happens, but I have no clue how I can have a function that returns a task without having a return value at two different locations? I haven't even tackled GetImageStream yet.

I am not even sure I took the right path into this...

Thank you!

2

There are 2 answers

4
Jayonas On BEST ANSWER

You're real close. A key point you might be missing is that then returns you a new task. So the last then in the chain determines the type of your task.

auto t = task<int>([] { return 0; });
// t is task<int>

auto t = task<int>([] { return 0; })
.then([](int i) { return 3.14; });
// t is task<double>

auto t = task<int>([] { return 0; })
.then([](int i) { return 3.14; })
.then([](double d) { return "foo"; });
// t is task<const char*>

If you just glance at the first line, it looks like you've always got a task<int>, but as you can see that's not necessarily the case if you immediately call then on it.

Secondly, keep in mind that your function is returning a task, not the stream itself. Usually you'd have the last then in your chain return you the task that you'll return from your function, and rather than store the task in a local variable, you just return it. For example:

task<const char*>
get_foo()
{
    return task<int>([] { return 0; })
    .then([](int i) { return 3.14; })
    .then([](double d) { return "foo"; });
}

Again, it looks a tad strange because a quick glance makes you think that you're returning a task<int>. This is nicely handled by using create_task rather than calling the task constructor explicitly. It frees you from having to explicitly specify the task's type at all. Additionally, it's easily changed to create_async if you instead want to return an IAsyncInfo derivative.

I'm not at all familiar with BitmapEncoder, but here's a tweaked version of your code that might do the trick:

Concurrency::task<IRandomAccessStream^> GetImageStream()
{
    auto stream = ref new InMemoryRandomAccessStream();
    return create_task(BitmapEncoder::CreateAsync(BitmapEncoder::JpegEncoderId, stream))
    .then([this, width, height, imageBytes](BitmapEncoder^ encoder)
    {
        // are width, height, and imageBytes member variables?
        // if so, you should only need to capture them OR "this", not both
        encoder->SetPixelData(BitmapPixelFormat::Rgba8, BitmapAlphaMode::Ignore, width, height, 96.0, 96.0, imageBytes);
        return encoder->FlushAsync();
    }).then([stream]()
    {
        // this should work fine since "stream" is a ref-counted variable
        // this lambda will keep a reference alive until it uses it
        return stream;
    });
}

The only real change is using create_task and immediately returning its result.

I'm still learning PPL myself, but one thing I've learned that's held up so far is that you should pretty much always be doing something with any task you create. The usual thing to do is use then to turn it into a new/different task, but you still need to do something with the task returned by the last then. Oftentimes you just return it, as above. Sometimes you'll add it to a container of tasks which are then grouped together with when_all or when_any. Sometimes you'll just call get() on it yourself to wait for its result. But the point is that if you create a task and don't do anything with it, then there's probably something wrong.

0
Erunehtar On

Also if anyone cares, here is how to implement the GetImageStreamRef mentioned above, in C++ version:

task<RandomAccessStreamReference^> GetImageStreamRef()
{
    return GetImageStream().then([](IRandomAccessStream^ pStream)
    {
        return RandomAccessStreamReference::CreateFromStream(pStream);
    });
}

Also, make sure to NEVER use .get() on any of those tasks, otherwise you will get an exception thrown saying "Illegal to wait on a task in a Windows Runtime STA". The proper way to get the value of this image stream reference is, for example on a DataRequestHandler that sets a bitmap data on the DataRequest:

void OnBitmapRequested(DataProviderRequest^ request)
{
    auto Deferral = request->GetDeferral();
    GetImageStreamRef().then([Deferral, request](RandomAccessStreamReference^ pStreamRef)
    {
        try
        {
            request->SetData(pStreamRef);
            Deferral->Complete();
        }
        catch( std::exception ex )
        {
            Deferral->Complete();
            throw ex;   //Propagate the exception up again
        }
    });
}