【问题标题】:Architecture decision: return std::future or provide a callback?架构决策:返回 std::future 还是提供回调?
【发布时间】:2020-11-03 20:53:38
【问题描述】:

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

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

  • 实现方法CaptureInterrupt,在发生中断时调用特定通道的回调;
  • 持有一个运行CaptureInterrupt的线程;
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的对象:

  • 实现方法SingleOutput,启动异步输出;
  • 实现方法WaitOutputFinished,等待通道运行,然后将其状态修改为stopped
  • 持有一个运行WaitOutputFinished的线程;

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 返回std::future 会很方便。对于第二种类型的 Arinc429 设备,我认为这样做没有问题,因为单独的通道对象拥有自己单独的等待线程。

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

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

选择什么选项?

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

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

c.定义两者。这合理甚至可能吗?这意味着调用std::promise&lt;R&gt;::set_value 而不是调用。


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

对于实现中断的设备,我没有看到一种清晰而简单的方法来实现返回std::future,因为所有通道都有一个公共中断事件和捕获线程。

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

【问题讨论】:

    标签: c++ multithreading c++11 asynchronous future


    【解决方案1】:

    我在熟悉ASIOExecutors 之前就问过这个问题。他们提供了一个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();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-13
      • 1970-01-01
      • 2020-12-29
      • 2016-06-27
      • 1970-01-01
      • 2022-01-15
      相关资源
      最近更新 更多