dimanche 31 juillet 2022

Timer thread implementation

I've implemented a timer thread whose job is to call functions at a certain interval. The functions may be called multiple times if their interval > 0 millisec otherwise only one time. Even if the timer thread is running, still a new function can be registered. Could you please provide your feedback and improvement factors?

class TimerThread
{
    using ClockType = std::chrono::high_resolution_clock;

public:
    TimerThread() = default;
    TimerThread(TimerThread const &) = delete;
    TimerThread & operator=(TimerThread const &) = delete;

    ~TimerThread() noexcept
    {
        try
        {
            stop();
        }
        catch(std::exception const &ex)
        {
            std::cout << "Exception: " << ex.what() << std::endl;
        }
        catch(...)
        {
        }
    }

    void registerCallback(std::function<void()> func, uint32_t const interval=0)
    {
        std::unique_lock<std::mutex> lock{mt_};
        timers_.emplace_back(std::move(func), ClockType::now(), interval);
        cv_.notify_all();
    }

    void start(uint32_t const delay=0)
    {
        if (! start_)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(delay));
            workerThread_ = std::thread{&TimerThread::run, this};
            start_ = true;
        }
    }

private:
    void run()
    {
        for (auto &t: timers_)
            t.prevFireTime_ = ClockType::now();

        while (startTimerFlag_.load(std::memory_order_acquire))
        {
            std::unique_lock<std::mutex> lock{mt_};
            cv_.wait(lock, [this]() -> bool {
                return ! timers_.empty();
            });

            for (auto &t: timers_)
                if (t.isReady())
                    t.func_();
        }
    }

    void stop()
    {
        startTimerFlag_.store(false, std::memory_order_release);
        cv_.notify_all();
        if (workerThread_.joinable())
            workerThread_.join();
    }

    struct TimerInfo
    {
        TimerInfo() = default;

        TimerInfo(std::function<void()> func, ClockType::time_point prevFireTime, uint32_t const interval):
            func_{std::move(func)},
            prevFireTime_{prevFireTime},
            intervalMilliSec_{interval}
        {
        }

        bool isReady()
        {
            if (!isFiredFirstTime)
            {
                isFiredFirstTime = true;
                return true;
            }
            else if (intervalMilliSec_ != 0)
            {
                auto current = ClockType::now();
                uint32_t const duration = std::chrono::duration_cast<std::chrono::milliseconds>(current - prevFireTime_).count();

                if (duration >= intervalMilliSec_)
                {
                    prevFireTime_ = current;
                    return true;
                }
            }

            return false;
        }

        std::function<void()> func_;
        ClockType::time_point prevFireTime_;
        uint32_t intervalMilliSec_;
        bool isFiredFirstTime{false};
    };

    std::vector<TimerInfo> timers_;
    std::thread workerThread_;
    std::mutex mt_;
    std::condition_variable cv_;
    std::atomic<bool> startTimerFlag_{true};
    bool start_{false};
};

int main()
{
    TimerThread timer;

    timer.registerCallback([](){
        std::cout << "Timer 1 - " << std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() << std::endl;
    }, 1000);

    timer.registerCallback([](){
        std::cout << "Timer 2 - " << std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() << std::endl;
    }, 2000);

    timer.registerCallback([](){
        std::cout << "Timer 3 - " << std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() << std::endl;
    });

    timer.start();

    std::this_thread::sleep_for(std::chrono::seconds(5));

    LOG("Terminating main()...");

    return 0;
}

Aucun commentaire:

Enregistrer un commentaire