Мне интересно, как разработать асинхронный API, используя обещания и фьючерсы.
Приложение использует один поток данных, который используется как для незапрашиваемых периодических данных, так и для передачи запросов / ответов.
Для блокирования запроса / ответа, пока ответ не получен, это не вариант, и я не хочу загружать код с помощью обратных вызовов, поэтому я хотел бы написать что-то вроде SendMessage, которое принимает идентификатор ожидаемого ответа и завершается только при получении. Звонящий должен прочитать ответ.
Кандидат API может быть:
std::future<void> sendMessage(Message msg, id expected)
{
// Write the message
auto promise = make_shared<std::promise<void>>();
// Memorize the promise somewhere accessible to the receiving thread
return promise->get_future();
}
Рабочий поток после получения сообщения должен иметь возможность запрашивать структуру данных, чтобы узнать, есть ли кто-то, ожидающий его, и «освободить» будущее.
Учитывая, что обещания не могут быть повторно использованы, я пытаюсь понять, какую структуру данных мне следует использовать для управления обещаниями «в полете».
Этот ответ был переписан.
Установка состояния общего флага может позволить работнику узнать, ожидает ли другая сторона, скажем, босса, результата.
Общий флаг вместе с обещанием и будущим может быть заключен в класс (шаблон), скажем, Request. Босс установил флаг, уничтожив свою копию запроса. И рабочий запрос, ожидает ли начальник все еще выполнения запроса, вызывая определенную функцию-член в его собственной копии запроса.
Одновременное чтение / запись на флаге, вероятно, должно быть синхронизировано.
Босс может не получить доступ к обещанию, а работник не может получить доступ к будущему.
Должно быть не более двух копий запроса, потому что будет установлен флаг уничтожения объекта запроса. Для достижения этого мы можем delcare соответствующие функции-члены как delete
или же private
и предоставить два экземпляра заявки на строительство.
Далее следует простая реализация запроса:
#include <atomic>
#include <future>
#include <memory>
template <class T>
class Request {
public:
struct Detail {
std::atomic<bool> is_canceled_{false};
std::promise<T> promise_;
std::future<T> future_ = promise_.get_future();
};
static auto NewRequest() {
std::unique_ptr<Request> copy1{new Request()};
std::unique_ptr<Request> copy2{new Request(*copy1)};
return std::make_pair(std::move(copy1), std::move(copy2));
}
Request(Request &&) = delete;
~Request() {
detail_->is_canceled_.store(true);
}
Request &operator=(const Request &) = delete;
Request &operator=(Request &&) = delete;
// simple api
std::promise<T> &Promise(const WorkerType &) {
return detail_->promise_;
}
std::future<T> &Future(const BossType &) {
return detail_->future_;
}
// return value:
// true if available, false otherwise
bool CheckAvailable() {
return detail_->is_canceled_.load() == false;
}
private:
Request() : detail_(new Detail{}) {}
Request(const Request &) = default;
std::shared_ptr<Detail> detail_;
};
template <class T>
auto SendMessage() {
auto result = Request<T>::NewRequest();
// TODO : send result.second(the another copy) to the worker
return std::move(result.first);
}
Новый запрос связан с функцией factroy NewRequest
возвращаемое значение std::pair
который содержит два std::unique_ptr
каждый хранит копию вновь созданного запроса.
Теперь работник может использовать функцию-член CheckAvailable()
проверить, отменен ли запрос.
И совместно используемое состояние управляется точно (я полагаю) std :: shared_ptr.
Обратите внимание на std::promise<T> &Promise(const WorkerType &)
: Ссылочный параметр const (который должен быть заменен типом propre в соответствии с вашей реализацией) предназначен для предотвращения случайного вызова боссом этой функции, в то время как рабочий должен иметь возможность легко предоставить аргумент propre для вызова этой функции. То же самое для std::future<T> &Future(const BossType &)
,
Других решений пока нет …