架构决策:return std::future 还是提供回调?
Architecture decision: return std::future or provide a callback?
我正在为 Arinc429 设备设计一个 API 接口。不同的供应商在设备中提供不同的功能,所以我决定有一组必须为每个实体实现的通用和预期的方法。
特别是,我对输出通道有疑问。它提供了一个字节数组的单次输出方法,方法调用后输出本身是异步的。其中一个设备实现了可以通过 OS 实用程序捕获的中断(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
到 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();
我正在为 Arinc429 设备设计一个 API 接口。不同的供应商在设备中提供不同的功能,所以我决定有一组必须为每个实体实现的通用和预期的方法。
特别是,我对输出通道有疑问。它提供了一个字节数组的单次输出方法,方法调用后输出本身是异步的。其中一个设备实现了可以通过 OS 实用程序捕获的中断(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
到 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();