lewissbaker / cppcoro

A library of C++ coroutine abstractions for the coroutines TS
MIT License
3.43k stars 469 forks source link

TaskCompletionSource ? #59

Open mediabuff opened 6 years ago

mediabuff commented 6 years ago

Other languages like C# have this. What is the equivalent in cppcoro ?

lewissbaker commented 6 years ago

There is not currently an equivalent for cppcoro tasks.

Usually, when you want to set a result for a task you just co_return theResult; or throw some_exception{}; from within the coroutine body.

If you need to be able to set the result from outside of the coroutine somehow then you can use some of the other synchronisation primitives to hook up something similar. eg. Write the result to a shared location then use an async_manual_reset_event to signal that a result is available and have the consumer await the event before reading the result from the shared location.

I have been thinking about how best to implement a task/task_completion_source or future/promise-style interface on top of coroutines and have a few ideas on how to do this. But I do wonder whether there are better abstractions we can build with coroutines or is something like this just going to need to be an essential tool in the toolbox?

Are you able to elaborate on your requirements for a task_completion_source-like API?

mediabuff commented 6 years ago

1) yes, you can use external shared variable and synchronization. But task_completion_source provides a nice packaging and makes the ‘intent’ clear 2) there are many use scenarios. C# and pplx provide it as it seems almost mandatory for async programming in winrt/windows 10/8 3) please see the scenario for CreateAyncWindow in http://www.informit.com/articles/article.aspx?p=2304072&seqNum=2

mediabuff commented 6 years ago

Also, please see this 'AcceptAsync https://stackoverflow.com/questions/15316613/when-should-taskcompletionsourcet-be-used

mediabuff commented 6 years ago

Also, some useful background https://blogs.msdn.microsoft.com/pfxteam/2009/06/02/the-nature-of-taskcompletionsourcetresult/

lewissbaker commented 6 years ago

Thanks for the links @mediabuff.

You may be right that it's needed in some cases for interacting with other APIs and event-based notification systems. But I do wonder whether some other abstraction or way of doing this would be a better fit for coroutines?

The design of a safe, general future/promise or task/task_completion_source almost mandates an extra heap allocation and requires some form of thread-synchronisation (eg. atomics or mutex) to arbitrate the race between the consumer awaiting the result/attaching the continuation and the producer setting the result.

In cases where the operation can be represented as an awaitable type and where the operation can be started inside await_suspend() then we should be able to avoid the thread-synchronisation, and possibly even avoid the heap allocation in some cases.

If we do end up implementing something like this then I don't think returning a task<T> is the best design as task<T> is designed to be a lazily executed operation. However, the design of a future/promise is not lazy - the operation may have already started executing before the consumer awaits the result.

Instead I think it should just return some other awaitable type (future<T>, maybe?). If you really need a task<T> then you can manufacture one with the cppcoro::make_task(awaitable) function.

One downside of promise/future-style APIs is that they leave the potential for detached computation. ie. cases where the consumer no longer needs the result and so destructs the future, but the producer is still executing and holding on to the 'promise' object. Without other synchronisation mechanisms that wrap the computation, it's no longer possible to determine when that computation will complete. If that computation is using resources that operations want to use then it makes it difficult to determine when those resources become available again without external synchronisation on those resources (eg. if it was reading a file then knowing when it's safe to delete that file requires waiting until the operation completes).

I have been trying to avoid detached computation in the design of cppcoro so far. It may turn out to be unavoidable when interacting with other asynchronous APIs, but I'd like as much as possible to try and avoid exposing APIs that enable detached computation.

I'll keep thinking about the design.

Edit: Tidied up and completed comment that was accidentally posted early (kids mashing on keyboard).

mediabuff commented 6 years ago

Thanks for researching this further and sharing your thoughts.

One downside of promise/future-style APIs is that they leave the potential for detached computation. ie. cases where the consumer no longer needs the result and so destructs the future, but the producer is still executing and holding on to the 'promise' object

Here is where CancellationTokenSource/CancellationToken is useful. https://www.infoq.com/articles/Tasks-Async-Await

lewissbaker commented 6 years ago

A cancellation_token can only be used to request cancellation of the computation. It can't be used to synchronise/wait-for that computation to respond to the cancellation request, release its used resources and complete.

I don't think you can use cancellation tokens to eliminate detached computation. Only reduce the time that a detached computation runs for once it has been detached and the result is no longer required.