Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to connect to a signal with specific argument? #59

Open
qiangxinglin opened this issue Nov 5, 2023 · 2 comments
Open

How to connect to a signal with specific argument? #59

qiangxinglin opened this issue Nov 5, 2023 · 2 comments

Comments

@qiangxinglin
Copy link

We can connect to a Qt signal by using

// [signal] Object::finished(const QString&)
auto output = QtPromise::connect(obj, &Object::finished)
    .then([](const QString& data) {
        // use the data here to do further work
    });

How to implement a promise only resolved when not only the signal emitted but also meet a specific condition?

// task with int task_id
QPromise<int> task = {...}

// [signal] Object::finished(const int&)
task.then([](const int& task_id){
  QObject* context = new QObject(this);
  return QtPromise::QPromise<Qstring>{[=](const auto& resolve, const auto& reject) {
      QObject::connect(this, &Object::finished, context, [=](const int& finshed_id) {
          if (task_id == finished_id)
          {
              resolve("task{} is finished, we can proceed to next", task_id);
              delete context;
          }
      });
  })

Would this work? Any elegant solution to handle the context deletion (for disconnect the lambda expression)?

@pwuertz
Copy link
Contributor

pwuertz commented Nov 5, 2023

How to implement a promise only resolved when not only the signal emitted but also meet a specific condition?

Concerning resource lifetime & promises, the easiest way to do this is to create a shared pointer to your resource and transfer it to the continuations where object lifetime is required. If you need some utility for managing signal-connections, you could also reuse the connection helper that QtPromise uses:
https://github.com/simonbrunel/qtpromise/blob/master/src/qtpromise/qpromiseconnections.h

In principle this is easy to do, but there are a few things to consider in this case. Cancellation is conceptually a tough problem to solve, so this situation begs the question: What's the alternative for continuing the promise if the finished-signal isn't guaranteed to do it? Maybe you can provide more context of your set up, perhaps there is an easier way to do this?

Just a guess: You're managing a collections of tasks with unique IDs and you'd like to attach continuations to them by ID. Isn't it possible to keep a record of task-promises by ID, look them up by ID and attach continuations to the matched tasks?

Another pattern I sometimes use is sort of a promise-response-pool for request+reply like situations. Say there is a service-instance with a makeRequest method. When calling makeRequest, the service creates a new promise, stores the resolver&rejecter using some internally assigned request-id, initiates some process using this ID, and returns the promise immediately to the caller. Later, if a process completion event is detected by id, the service fetches the corresponding resolver and calls it. If the service is stopped or destroyed, all residual rejecters are called. Maybe this is a good fit for your situation?

@qiangxinglin
Copy link
Author

qiangxinglin commented Nov 6, 2023

@pwuertz Thank you for your advise!

Let me introduce my requirement more specific:

  • I have 2 MQ, say 1 publisher and 1 subscriber. Both connect to a remote host.
  • The publisher should offer 2 interface: Send(Task, Result&) & Post(Task)
    • Post: give this task a UUID, post the task through the MQ, and register a handler (with a timeout) in the subscriber for modifying the Result&. This method should return when the post is successfully done but the task result has not been fullfilled yet.
    • Send: first Post the task, and keep blocking until recv the task result from the subscriber or timeout reached.
  • The subscriber is responsible for kept polling messages from the MQ, parse the UUID in it, and decide whether this is a Send/Post task, then perform the relevant continuations. If the timeout of this task has been reached, silently drop this message.
QPromise<void> MyPost(TaskArg arg) {
  auto id = QUuid::createUuid().toString().toStdString();
  PACK_UUID_INTO_TASK_ARG
  QPromise<void> publish = m_publisher->Publish(arg);
  auto context = new QObject;
  publish
    .then([=] {
        return QPromise<json>{[=](const auto& resolve, const auto& reject) {
            connect(
                m_subscriber, &Subscriber::sigRecv, context, [=](const json& j) {
                    if (j["uuid"] == id)
                    {
                        resolve(j);
                        delete context;
                    }
                },
                Qt::DirectConnection);
        }};
    })
    .timeout(100ms)
    .then([](const json& j) { SOME_CONTINUATION })
    .fail([=](const QtPromise::QPromiseTimeoutException& error) {
        delete context;
        LOG_WARN("Timeout for task {}", id);
    });
  return publish; // return the publish promise
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants