17 SignaledTask

Hosts a thread with explicit Start/Stop functions. Calls to Signal() cause the given task to be executed by the worker thread. Note however that by design there is no requirement that there will be one execution of the task for each call to Signal() (i.e. that the calls will be paired). Rather, the implementation only records the signaled state with a boolean, not a counter for the number of calls to Signal().

If while a task is executing there are multiple calls to Signal() then the task will only be made to execute one more time by those calls to Signal().

SignaledTask.h

// SignaledTask.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2013

#pragma once
#ifndef Ceda_cxThread_SignaledTask_H
#define Ceda_cxThread_SignaledTask_H

#include "cxThread.h"
#include "AutoResetEvent.h"
#include "ThreadName.h"
#include <thread>
#include <functional>
#include <atomic>

#ifdef _MSC_VER
    // struct 'X' needs to have dll-interface to be used by clients of class 'Y'
    #pragma warning(disable:4251)
#endif

namespace ceda
{
class cxThread_API SignaledTask
{
public:
    SignaledTask();
    ~SignaledTask();

    void Start(const char* threadName, std::function<void()> task);
    void Stop();
    void Signal();

    // A task can call this to poll whether it should complete as soon as possible
    bool Abort() const { return !started_; } 

private:
    xstring threadName_;
    std::thread thread_;
    volatile bool started_;

    // Event used to wake up the thread from its dormant state.  This can be for two reasons
    //    1.    The thread is being stopped
    //    2.    It is time to perform run the task
    AutoResetEvent wakeThreadEvent_;

    // Task to be executed when Signal() is called
    std::function<void()> task_;
};
} // namespace ceda
#endif // include guard

SignaledTask.cpp

// SignaledTask.cpp
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2013

#include "SignaledTask.h"

namespace ceda
{
SignaledTask::SignaledTask() : started_(false) {}

SignaledTask::~SignaledTask()
{
    cxAssert(!started_);
}

void SignaledTask::Start(const char* threadName, std::function<void ()> task)
{
    threadName_ = threadName;
    task_ = task;
    cxAssert(!started_);
    started_ = true;
    thread_ = std::thread(
        [this]
        {
            SetThreadName(threadName_.c_str());
            while(1)
            {
                wakeThreadEvent_.Wait();
                if (!started_) return;
                task_();
            }
        });
}

void SignaledTask::Stop()
{
    cxAssert(started_);
    started_ = false;
    wakeThreadEvent_.Signal();
    thread_.join();
}

void SignaledTask::Signal()
{
    cxAssert(started_);
    wakeThreadEvent_.Signal();
}
} // namespace ceda

SignaledTaskTests.cpp in txThread

Even though the test calls Signal() six times, the task is only called three times: Generated output:

UnitTestSignaledTask
    task() called
    task() called
    task() called
// SignaledTaskTests.cpp
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2013

#include "Ceda/cxThread/SignaledTask.h"
#include "Ceda/cxUtils/Tracer.h"
#include "Ceda/cxUtils/HPTime.h"

void UnitTestSignaledTask()
{
    Tracer() << "UnitTestSignaledTask\n";
    
    ceda::SignaledTask st;
    st.Start("UnitTestSignaledTask", 
        []()
        {
            Tracer() << "    task() called\n";
        });

    st.Signal();
    st.Signal();
    st.Signal();
    Sleep(10);

    st.Signal();
    Sleep(10);

    st.Signal();
    st.Signal();
    Sleep(10);

    st.Stop();
}