设计结合同步和异步操作的C ++ API

时间:2016-11-08 15:04:44

标签: c++ asynchronous

我正在设计一个公开同步和异步操作的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: %s\n", 
                    error->code, error->description.c_str());
        } else {
            printf("Operation completed successfully. Result: %s\n", 
                    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以返回其结果/失败。这种方法在同步和异步操作中更加一致,看起来更清晰。另一方面,使用Callback进行简单的同步操作感觉有点奇怪。
  2. 使用std::promisestd::future,并使用例外报告失败。对于异步操作,将返回std::future,并在发生故障时抛出get()。同步操作只会在出现故障时抛出。这种方法感觉更清晰,因为错误处理不会打击方法签名,异常是在C ++中进行错误处理的标准方法。但是,为了获得结果,我将不得不调用future::get(),所以如果我不想阻止,那么我必须启动另一个线程来等待结果。异步操作的延续在实际设置std::promise上的结果的线程上运行也很重要。这种方法是这个问题的公认答案 - Synchronous and ASynchronous APIs
  3. 我想知道:

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

1 个答案:

答案 0 :(得分:1)

  • 问:如果可以避免替代#2中的额外线程。
  • 答:避免额外线程的一种方法是将线程池与任务队列一起使用。该方法仍然使用额外的线程,但线程的数量是系统固定的,而不是与创建的任务数量成比例。

  • 问:如果替代#2的缺点超过其优点(特别是额外的线程)。

  • 答:我不这么认为。

  • 问:如果还有其他方法我没有考虑过。

  • 答:是的。在我看来,最好的方法是使所有内容异步,为boost :: asio提供类似的API,利用boost :: asio :: io_service和lambda函数,并实现线程池和任务队列。您可以通过异步实现sync作为包装器,如下所示:进行异步调用并等待std :: condition_variable。异步调用的回调信号表示condition_variable。

  • 问:哪种方法可视为可读性和一致性的最佳选择。

  • 答:异步代码不会像同步代码那样可读。如果可读性对您来说真的很重要,请放弃异步。另一方面,如果您想要异步和一致性,请将所有内容都与上面概述的一样异步。

除此之外,在我看来,你不应该实现自己的Error类。看看std :: error_code(以及error_condition,error_category)。一篇不错的文章是http://blog.think-async.com。有人已经为你解决了这个问题。