设计一个结合同步和异步操作的c++ API

Design for a C++ API combining synchronous and asynchronous operations

本文关键字:同步 异步操作 API c++ 结合 一个      更新时间:2023-10-16

我正在设计一个暴露同步和异步操作的c++ API。所有操作都可能失败,必须报告失败。异步操作必须提供一种在完成时执行延续的方法。我正在努力以最易读和一致的方式设计API。

这是一个例子,说明我现在的设计:

#include <memory>
#include <future>
using namespace std;
class Error {
public:
    Error(int c, string desc) : code(c), description(desc) {} 
    int code;
    string description;
};
template<typename T>
class Callback {
public:
    virtual void completed(const T& result, unique_ptr<Error> error) = 0;
};
template<typename T>
class PrintCallback : public Callback<T> {
public:
    void completed(const T& result, unique_ptr<Error> error) override {
        if (nullptr != error) {
            printf("An error has occured. Code: %d Description: %sn", 
                    error->code, error->description.c_str());
        } else {
            printf("Operation completed successfully. Result: %sn", 
                    to_string(result).c_str());
        }
    }
};
class API {
public:
    void asyncOperation(shared_ptr<Callback<int>> callback) {
        thread([callback]() {
            callback->completed(5, nullptr);
        }).detach();
    }
    int syncOperation(unique_ptr<Error>& error) {
        return 5;
    }
    void asyncFailedOperation(shared_ptr<Callback<int>> callback) {
        thread([callback]() {
            callback->completed(-1, unique_ptr<Error>(new Error(222, "Async Error")));
        }).detach();
    }
    int syncFailedOperation(unique_ptr<Error>& error) {
        error = unique_ptr<Error>(new Error(111, "Sync Error"));
        return -1;
    }
};
我不喜欢在同步操作中使用error out参数,以及同步和异步签名之间的不一致。我正在讨论两种选择:
  1. 将同步操作视为异步操作,并让它们接受回调以返回结果/失败。这种方法在同步和异步操作之间更加一致,并且看起来更干净。另一方面,让一个简单的同步操作与Callback一起工作感觉有点奇怪。
  2. 使用std::promisestd::future,并使用异常报告故障。对于异步操作,将返回std::future,并在失败的情况下抛出其get()。同步操作将在失败的情况下抛出。这种方法感觉更干净,因为错误处理不会干扰方法签名,异常是c++中进行错误处理的标准方法。然而,要获得结果,我必须调用future::get(),所以如果我不想阻塞,那么我必须启动另一个线程来等待结果。异步操作的延续运行在实际在std::promise上设置结果的线程上,这一点也很重要。这种方法是对这个问题的公认答案——同步和异步api。

我想知道:

  1. 如果选项#2中的额外线程可以避免。
  2. 如果替代方案#2的缺点大于优点(特别是额外的线程)。
  3. 如果有其他方法我没有考虑过。
  4. 哪种方法被认为是可读性和一致性的最佳选择。

谢谢!

    Q:是否可以避免选项#2中的额外线程
  • A:避免额外线程的一种方法是将线程池与任务队列一起使用。这种方法仍然使用额外的线程,但是线程的数量是固定的,而不是与创建的任务数量成比例。

  • 问:如果替代方案#2的缺点大于优点(特别是额外的线程)

  • A:我不这么认为。
  • :是的。在我看来,最好的方法是让一切都异步,为boost::asio提供类似的API,利用boost::asio::io_service和lambda函数,并实现线程池和任务队列。您可以将sync作为async的包装器实现如下:进行async调用并等待std::condition_variable。async调用的回调信号是condition_variable.

  • 问:哪种方法被认为是可读性和一致性的最佳选择

  • 答:异步代码不会像同步代码那样可读。如果可读性对你来说真的很重要,那就放弃异步。另一方面,如果你想要异步和一致性,就像我上面概述的那样,让一切都是异步的。

除此之外,在我看来你不应该实现你自己的Error类。看看std::error_code(以及error_condition、error_category)。一篇好文章是http://blog.think-async.com。已经有人帮你弄明白了。