1

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!

Erunehtar
  • 1,583
  • 1
  • 19
  • 38
  • Are you compiling the above code as C++/CLI code or plain C++ code? It's not going to work as plain C++ code as the caret signatures for garbage collected reference types are a C++/CLI extension. – Timo Geusch May 23 '13 at 17:49
  • @TimoGeusch: it's not C++/CLI, it's C++/CX: http://programmers.stackexchange.com/questions/162168/what-are-c-cx-and-c-cli-and-how-do-they-relate-to-c-and-winrt – Matt May 24 '13 at 13:49
  • @TimoGeusch Yes I am compiling as C++/CX. Thanks. – Erunehtar May 29 '13 at 20:15

2 Answers2

3

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.

Jayonas
  • 401
  • 2
  • 12
  • Thanks, your answer was extremely insightful! In fact, I was able to make it all work now! :) – Erunehtar May 29 '13 at 19:57
  • One last detail, the function GetImageStream should return a InMemoryRandomAccessStream^ instead of a IRandomAccessStream^ otherwise you get a compile error. – Erunehtar May 29 '13 at 20:09
  • Great, I'm glad you got it working! One minor point on your answer (which I can't seem to comment on directly): you only get the exception you mention from `.get()` when you call it **on the UI thread**. This is WinRT's way of not letting you stall the UI to wait for a long-running operation. If you're in a background thread, though, you can call `.get()` as necessary. – Jayonas May 30 '13 at 01:07
  • Another minor comment on the code in your answer: you may want to use `catch (...)` to catch all exceptions, not just those that derive from `std::exception`. You can re-throw the same exception from such a catch block by just using `throw;` with no argument. – Jayonas May 30 '13 at 01:19
0

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
        }
    });
}
Erunehtar
  • 1,583
  • 1
  • 19
  • 38