qtpromise
qtpromise copied to clipboard
API question: Is there value_or(...) equivalent?
I wanted to know if there exists an API similar to value_or(...) that would allow me to do something like:
struct Something { int interesting{0}; };
int getter() {
QtPromise::QPromise<Something> promise = ...; // Obtained somehow
return promise.wait().value_or(Something{}).interesting;
}
I often find myself doing something similar to:
int getter() {
QtPromise::QPromise<Something> promise = ...;
int outerInteresting = 0;
promise.then([&](const Something& s) { outerInteresting = s.interesting; }.wait();
return outerInteresting;
}
Would an addition like value_or() be useful for the library?
Btw. I was also thinking that to_optional() could also fit my needs but that would require C++17 (and AFAIK the library aims to support C++11). What is the authors' current attitude towards conditional compilation and feature testing (if the compiler supports C++17)?
You are trying to retrieve an asynchronous promise result in a syncronous way. There is an example for this in the tests:
https://github.com/simonbrunel/qtpromise/blob/461f09bef8547d8696f909488afa31b399b9c78d/tests/auto/qtpromise/shared/utils.h#L14
But generally, this is not what promises are made for. I'd even go as far as saying that using wait() in production code is dangerous, since you might end up in reentrancy situations you weren't planning for.
@pwuertz What I have is an async API which I need to make (ocasionally) synchronous to interoperate with the rest of the system.
To achieve this, I'd need some equivalents of sync_wait() and value_or().
The question I'm trying to ask is whether these kind of member functions would be useful to have in the API (instead of having my own free functions that would do the same).
Would an addition like
value_or()be useful for the library?
I'm not sure it would be really useful since, as @pwuertz mentioned, the .wait() API is probably not a good practice (I initially implemented it for the unit tests). Maybe there are some situations where we know for sure that the promise is resolved, thus that method would make easier retrieving the value, though I don't think of any good example.
I see that bluebird provides APIs to inspect the promise values so it may make sense to have similar methods. However, value() should throw an error if the promise is not fulfilled. The value() method could have an optional argument as fallback in case the promise is rejected, which would be a shorthand for promise.fail(()[] { return -1; }).value() (written promise.value(-1)).
I was also thinking that
to_optional()could also fit my needs
I don't get how an optional would make things better (you would still need to call .wait(), right?). Can you provide an example?
Maybe there are some situations where we know for sure that the promise is resolved, thus that method would make easier retrieving the value, though I don't think of any good example.
There is indeed one good example I came across recently. I've been playing around with C++ co-routines and implemented a co_await for QPromise objects.
The spec defines a await_ready() method which allows you to skip the suspension of a co-routine if you know that the awaitable is done, i.e. if QPromise is not pending. In this situation you'd require sync access to the QPromise result value (or exception) for implementing the await_resume() method.
...
value()should throw an error if the promise is not fulfilled.
Indeed this is also what python does with asyncio futures. If a promise/future is fulfilled or rejected, you can collect the result or the exception synchronously with future.result() and future.exception(). Doing this with a pending future fails with an InvalidStateError exception.
@pwuertz @simonbrunel Could you advise what would be the best way to implement a wait() equivalent that would be safe for production code?
I'm asking because I'm facing exactly these kind of reentrancy issues when calling wait(). What I want to achieve, in principle, is to have an "async API" which I could ocasionally use in a synchronous way.
I was trying to think on an answer, and the two ideas I have:
- introduce another thread with an event loop that would be used to "resolve/reject" the promise
- not use QPromise for that approach
Could you advise what would be the best way to implement a
wait()equivalent that would be safe for production code?
Generally, there is no way to do this. Think of it like some sort of logical-fallacy. You can't (and shouldn't) block-wait on something that relies on being used in a non-blocking context.
I'm asking because I'm facing exactly these kind of reentrancy issues when calling
wait(). What I want to achieve, in principle, is to have an "async API" which I could ocasionally use in a synchronous way.
Asynchronous programming is extremely 'viral'. The only viable way to do this is by refactoring the synchronous code, e.g. by making it async or by using classic continuation patterns like callbacks.
introduce another thread with an event loop that would be used to "resolve/reject" the promise
This may work in specific situations, but can't work in general. In Qt for example there is only one GUI/main thread. If you block this one, any promise waiting for a signal from there won't ever resolve. In order to do this safely, you as a developer require exact knowledge about which promise is safe to be-sync-blocked at which line using which thread.
- not use QPromise for that approach
This is a general design issue which isn't specific to QPromise. As a reference, you can't sync block Python Async-Coros or JavaScript async functions either.
@mkucmpro "ocasionally use in a synchronous way" might work in specific situations if you're able to control and contain the event emitter in a thread. For example, you can wrap an asynchronous 'download-this-file'-function in a sync function by creating a socket and a worker thread, then wait for the completion of the transfer synchronously in the calling thread. I'd consider this to be "safe" since the user of this method can't mix up thread affinity of events and handlers, or inject anything that might run in an unexpected context. So the async function / qpromise object would be contained in the worker thread loop, and the sync function would communicate the result as QFuture for example. This pattern requires that you create a dedicated thread for each io-bound async task, which is something async programming tries to eliminate.