Я разрабатываю интерфейс API для устройств Arinc429. Различные поставщики предоставляют различные функции в устройствах, поэтому я решил иметь набор общих и ожидаемых методов, которые необходимо реализовать для каждого объекта. В частности, у меня вопрос по выходному каналу. Он предоставляет метод для единственного вывода массива байтов, а сам вывод является асинхронным после вызова метода. Одно из устройств реализует прерывания, которые можно перехватить с помощью утилит ОС (WaitForSingleObject
в Windows).
Итак, в этом конкретном случае у меня есть объект, который реализует 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();
}
}
Но другое устройство не поддерживает прерывания, поэтому мне приходится «занято-ждать» (спать на рассчитанное ожидаемое время, затем l oop засыпать на небольшие промежутки времени, чтобы исправить возможные неточности).
Объект, реализующий интерфейс 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;
};
Учитывая асинхронный характер вывода, думаю, было бы удобно возвращать std::future
из SingleOutput
. И я не вижу проблем в этом для второго типа устройства Arinc429, поскольку отдельные объекты каналов владеют своими собственными отдельными ожидающими потоками.
Я решил добавить обратный вызов для готового вывода с самого начала, так как прерывания реализованы для первого устройства. Также обратные вызовы удобны для выдачи сигналов Qt из.
Но std::future
более удобен для синхронизации и может использоваться для ожидания вывода на fini sh. Хотя им также можно управлять с помощью обратных вызовов и переменных условий, я не считаю этот подход достаточно удобным.
Какой вариант выбрать?
a. Определите процедуру, которая регистрирует и использует обратные вызовы.
b. Определите std::future
как тип возврата для SingleOutput
.
c. Определите оба. Это разумно или вообще возможно? Подразумевается позвонить std::promise<R>::set_value
, а потом позвонить.
Другой вопрос касается реализации.
Я не вижу ясного и простого способа реализовать возврат std::future
в случае устройства, реализующего прерывания, поскольку существует общее событие прерывания и захватывающий поток для всех каналов.
Как предоставить фьючерсы для нескольких объектов выходных каналов, которые все находятся в разных потоках? См. ArincPCI429_3::CaptureInterrupt()