Callback driven and pplx

Jul 14, 2015 at 11:53 AM
Hi,
I have some callback-driven C library doing some I/O asynchronously and wrapped with C++ class which provides synchronization mechanism using promise/future. The async C++ member function creates promise, binds it to callback which will set the value and the aforementioned member function returns future on which the user can wait for data to be received. Since I'm very enthusiastic with pplx and continuations I would like to convert above C++ class async member function to return pplx::task<MyData>. Is it possible? how do I set the result from callback to task's data as I do now with promise?
Coordinator
Jul 15, 2015 at 5:19 PM
Hi kreuzerkrieg,

I think what you are looking for is the pplx::task_completion_event. Basically it is an event that you can signal, optionally with some data, later on. A task can then be created from the task_completion_event, using the task constructor or pplx::create_task, which will be completed when the backing event is signaled. So you can create APIs like the following:
pplx::task<int> MyAPI()
{
    task_completion_event<int> tce; // Signal this event later on with the task_completion_event::set(...) API
    return pplx::create_task(tce);
}
You can also take a look at some of the pplx task tests to see some more examples. For example the TestTaskCompletionEvents_basic test case shows some very basic usage.

Steve
Jul 15, 2015 at 7:18 PM
Looks like this is what I need. Will check it tomorrow.
Thanks Steve!
Jul 20, 2015 at 5:57 AM
Additional question. According given examples I came out with following
struct Result
{
    Result(pplx::task_completion_event<std::vector<int>> completion) : data(), completionEvent(completion) {}
    std::vector<int> data;
    pplx::task_completion_event<std::vector<int>> completionEvent;
};

pplx::task<Result> MyAPI()
{
    return pplx::create_task([]()
                             {
                                 pplx::task_completion_event<Result> tce;
                                 auto retVal = pplx::create_task(tce);
                                 Result results(tce);
                                 ScheduleWork(&results);
                                 return retVal;
                             });
}

void ScheduledWorkClbk(void* cookie, SomeResult* result)
{
    auto results = static_cast<Result*>(cookie);
    results->data.push_back(result->intData);
    if(allWorkCompleted())
        results->completionEvent.set(results->data);
}
First question, is it a right way to use task with completion event as in function MyAPI?
Second, when declaring on stack "Result results(tce);" obviously it will be destroyed by the time the ScheduledWorkClbk will be called. Sure I can arrange the survival of it but maybe there is a clever way in pplx to preserve "local" variable that I need later?
Coordinator
Jul 20, 2015 at 5:13 PM
Hi kreuzerkrieg,

For the first question - don't call that initial create task, it is unnecessary unless your ScheduleWork API is doing some expensive computation or some sort of blocking. Which I assume not since it is called Schedule. Instead just return the task from the completion event. Something like this:
pplx::task<Result> MyAPI()
{
    pplx::task_completion_event<Result> tce;
    Result results(tce);
    ScheduleWork(&results);
    return pplx::create_task(tce);
}
For the second question - It sounds like you should use a shared_ptr or a unique_ptr depending on what the semantics are for you API.

Steve
Jul 20, 2015 at 6:24 PM
1) ScheduleWork can block, that's why I put it inside new task. But is this construct ok? to create a task, then inside it create an event which will be passed to a new nested task?
2) nah, no managed ptr would work, it is created on stack and passed by pointer - reference count not increased so the smart/unique pointer will be destroyed as soon as the scope is left. no problem, I can handle it, just thought the pplx may have some cunning solution for such a case.
Coordinator
Jul 20, 2015 at 6:27 PM
1) Yes in this case then what you have looks good. I just wanted to make sure you weren't creating an extra unnecessary task. This will still work.

2) Right I don't see how you can put your Result data on the stack here, you need to allocate it on the heap and use a smart pointer. But it sounds like you already have some idea of what you want to do.

Steve
Jul 20, 2015 at 6:36 PM
Great!
Thanks Steve!