架构决策:return std::future 还是提供回调?

Architecture decision: return std::future or provide a callback?

我正在为 Arinc429 设备设计一个 API 接口。不同的供应商在设备中提供不同的功能,所以我决定有一组必须为每个实体实现的通用和预期的方法。 特别是,我对输出通道有疑问。它提供了一个字节数组的单次输出方法,方法调用后输出本身是异步的。其中一个设备实现了可以通过 OS 实用程序捕获的中断(Windows 中的 WaitForSingleObject)。

所以在这种特殊情况下,我有一个实现 IArinc429Device 的对象,它:

void ArincPCI429_3::CaptureInterrupt()
{
    HANDLE hEvent = CreateEvent(nullptr, TRUE, FALSE, "PCI429_3Interrupt");
    // register event handle within device via DeviceIoControl

    while (true)
    {
        DWORD waitRes = WaitForSingleObject(hEvent, INFINITE);
        ResetEvent(hEvent);

        // get output channel index which has generated an interrupt
        size_t channelIndex = ...;

        // private implementation holds pointers to output channels
        pImpl_->GetChannelOut(channelIndex)->InvokeInterrupCallback();
    }
}


但是另一个设备没有实现中断,所以我不得不“忙等待”(睡眠计算出预期的时间,然后循环睡眠一小段时间以调整可能的不准确)。

实现接口的对象 IArinc429ChannelOutput:


void ArincECE206_1ChannelOutput::WaitOutputFinished(size_t words)
{
    // calculate expected period of time to sleep, using amount of words to transfer and channel output speed
    std::chrono::microseconds timeToSleep = ...;

    // 99% is enough
    std::this_thread::sleep_for(timeToSleep);

    // if channel is still running, wait for 1 more word.
    timeToSleep = Arinc429Values::TimeToTransfer(
            refImpl_.outputFreqs[ChannelIndex()],
            1);

    while(IsRunning())
    {
        std::this_thread::sleep_for(timeToSleep);
        ++additionalWords;
    }

    mode_ = Arinc429OutMode::stopped;

    if (callbackOnFinishedOutput_)
        callbackOnFinishedOutput_();
}

这是输出通道

的API的一部分
struct ARINC429_API IArinc429ChannelOutput
{
    // 0 based index
    virtual size_t ChannelIndex() const = 0;
    virtual Arinc429OutMode OutputMode() const = 0;
    virtual Arinc429Freq Frequency() const = 0;
    virtual size_t BufferSize() const = 0;

    virtual void SetFinishOutputCallback(std::function<void()>&& fCallBack) = 0;

    // elements exceeding BufferSize are ignored
    // TODO: return future?
    virtual bool SingleOutput(array_view<const uint32_t> wordArray) = 0;

    virtual void StopOutput() = 0;

    virtual ~IArinc429ChannelOutput() = default;
};

鉴于输出的异步性质,我认为从 SingleOutput 到 return std::future 会很方便。对于第二种类型的 Arinc429 设备,我认为这样做没有问题,因为单独的通道对象拥有自己单独的等待线程。

我选择从一开始就在完成的输出上添加回调,因为中断是为第一个设备实现的。回调也很方便从中发出 Qt 信号。

但是 std::future 更便于同步,可用于等待输出完成。虽然使用回调和条件变量也可以管理,但我觉得这种方法不太方便。

选择什么选项?

一个。定义注册和使用回调的例程。

b.将 std::future 定义为 SingleOutput 的 return 类型。

c.定义两者。这是否合理甚至可能?这意味着调用 std::promise<R>::set_value 而不是调用.


另一个问题是关于实施的。

对于实现中断的设备,我没有看到实现 returning std::future 的清晰简单的方法,因为所有中断事件和捕获线程都是通用的频道。

如何为所有驻留在不同线程中的多个输出通道对象提供期货?参见 ArincPCI429_3::CaptureInterrupt()

我在熟悉ASIO and Executors. They provide a Universal Asynchronous Model API之前就问过这个问题。也就是说,根据完成标记,模板函数可以同时同步和异步。

基本上,一个函数看起来像

template <typename CompletionT>
auto do_stuff(CompletionT &&token);

根据令牌的类型,可以通过简单的回调返回结果:
do_stuff([](bool res){ return res;});

如果需要同步行为,可以传递一个特殊的标签:

auto boolFuture = do_stuff(use_future);
bool res = boolFuture.get();