Архитектурное решение: вернуть std :: future или предоставить обратный вызов? - PullRequest
2 голосов
/ 14 июля 2020

Я разрабатываю интерфейс 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()

...