stlab / libraries

ASL libraries will be migrated here in the stlab namespace, new libraries will be created here.
https://stlab.cc
Boost Software License 1.0
660 stars 65 forks source link

Express the type of `promise` #530

Closed chenziliang closed 11 months ago

chenziliang commented 11 months ago

Hi,

I have an async job to execute, so would like to create a future / promise pair via stlab::package(...) in the function which return thefuture to caller and pass the promise to a different thread to do the job async and once the job is complete (with error), call promise to fulfill the future.

The code structure is like below. I would like to pass promise to an async_func which execute in a different thread. Would like to know how can i get the type signature of promise ? (Currently i wrap it in another lambda function to workaround the type problem but it seems not efficient due to another wrap.

void async_execute(std:function<int(int)> job) 
{
    auto p = stlab::package<int(int)>(default_executor, job);
    auto promise = p.first;
    async_func(promise); /// Pass the promise to `async_func` which execute some heavy task and finally invokes `promise(n)`.

   return p.second;  /// return future which caller can poll the result
}

Fundamentally, I would like to create a future / promise pair, return future for caller to wait for result and execute a heavy duty task in background and after the task is done, fulfill future by invoking the promise. May I ask if using stlab::package(...) is good for this purpose ?

Thanks !

chenziliang commented 11 months ago

BTW, passing an executor to package(...) seems an API surprise since the passed executor seems not used in regular cases. From the document https://stlab.cc/libraries/concurrency/future/package.html

The purpose of the passed `executor` is to have already an executor for an attached continuation.

May I ask if there is an example of an attached continuation ?

sean-parent commented 11 months ago

The promise type is packaged_task<Args...> - for example:

    auto [promise, future] = stlab::package<int(int, int)>(stlab::immediate_executor, [](int x, int y){ return x + y; });
    stlab::packaged_task<int, int> new_promise{std::move(promise)};

    new_promise(42, 10);
    cout << *future.get_try();

The reason why package takes an executor is because future have a default executor for the next continuation. So in the above, if we replace the last line with:

    future | [](int x){ cout << x; };

This continuation will execute immediately after the packaged task is invoked or when it is attached. I'm not thrilled with the idea of futures having an executor that is used if none is specified. The prior version always used an immediate executor but that was very problematic because with concurrency the context is not well defined (either the context of the promise or the context where the continuation is attached). I think the sender/receiver model is a better design where continuation chains aren't executed until marked ready. If a schedular/executor always starts the chain this is unambiguous.

chenziliang commented 11 months ago

Thanks @sean-parent !