C++ Implementing Timed Callback Function

c++ Implementing Timed Callback function

Many folks have contributed good answers here on the matter, but I will address the question directly, because I had a similar problem a couple of years ago. I could not use Boost for several reasons--I know Boost has excellent use in a lot of open source software. Moreover, I really wanted to understand timers and callbacks specifically as it pertains to Linux based environments. So, I wrote my own.

Fundamentally, I have a Timer class and a TimerCallback class. A typical callback, implemented as a inherited class of the TimerCallback class, will place the operations to be executed upon callback in the triggered () method, implemented specifically for the needs.

Per the usual semantics, a Timer object is associated with a callback object, which presumably contains all the required information needed for the callback to execute. The timer scheduling is managed by one environment-wide timer minheap which has to be maintained in a separate thread/process. This minheap task does only one thing: it minheapifies the minheap of callback events set in the future. A minheap selects the next event to fire in O(1) and can minheapify the remaining in O(log n) for n timer events. It can also insert a new timer event in O(log n) (Read a gentle introduction to heaps here).

When a timer fires, the minheap scheduler checks if it is a periodic timer, one shot timer or a timer that will execute a specific number of times. Accordingly, the timer object is either removed from the minheap or reinserted back into the minheap with the next execution time. If a timer object is to be removed, then it is removed from the minheap (but the timer object deletion may or may not be left to the task that created it) and the rest of the heap is minheap-ified; i.e., rearranged to satisfy the minheap property.

A working and unit tested implementation is here, and may contain bugs (excerpted from my application), but I thought it may help someone. The implementation is multi-process (fork()ed-process) based (and also uses pthreads in the main task (process)), and uses POSIX shared memory and POSIX message queues for communication between the processes.

Implementing timer callback in c

How should you approach this problem?

As with all problems:

  • Figure out what you want to do precisely
  • Ask yourself how you would do it yourself if you needed to (and if you could)
  • Try to formalize previous step as close as possible to single statements sentences, one step at a time.
  • Identify what blocks you / what you don't know how to do. Do some research on that.

What you want to do precisely

You want to "create a timed callback". I think this means :

  • You have a function foo doing some work
  • You have your program running
  • You want to be able to say "From right now, in X miliseconds, call foo" anywhere in your program

How would you do this yourself?

I think you would, for example, launch a stopwatch with X miliseconds then keep doing whatever you were doing. When the stopwatch reaches zero, you stop what you do and do the thing needed. Finally, resume what you were doing.

What blocks you?

Judging by your question I think two things block you:

  • You need to understand how to do function callbacks in C. See "function pointers"
  • You need to understand how to have a timer and do something when the timer reaches zero.

A few google searches will help you with both.

C Timer Callback

SetTimer in Win32 with a TimerProc as the call back.

/* calls TimerProc once every 60000 milliseconds */
SetTimer(NULL, 1, 60000, TimerProc);

C++ callback timer implementation

Rather than using threads you could use std::async. The following class will process the queued strings in order 4 seconds after the last string is added. Only 1 async task will be launched at a time and std::aysnc takes care of all the threading for you.

If there are unprocessed items in the queue when the class is destructed then the async task stops without waiting and these items aren't processed (but this would be easy to change if its not your desired behaviour).

#include <iostream>
#include <string>
#include <future>
#include <mutex>
#include <chrono>
#include <queue>

class Batcher
{
public:
Batcher()
: taskDelay( 4 ),
startTime( std::chrono::steady_clock::now() ) // only used for debugging
{
}

void queue( const std::string& value )
{
std::unique_lock< std::mutex > lock( mutex );
std::cout << "queuing '" << value << " at " << std::chrono::duration_cast< std::chrono::milliseconds >( std::chrono::steady_clock::now() - startTime ).count() << "ms\n";
work.push( value );
// increase the time to process the queue to "now + 4 seconds"
timeout = std::chrono::steady_clock::now() + taskDelay;
if ( !running )
{
// launch a new asynchronous task which will process the queue
task = std::async( std::launch::async, [this]{ processWork(); } );
running = true;
}
}

~Batcher()
{
std::unique_lock< std::mutex > lock( mutex );
// stop processing the queue
closing = true;
bool wasRunning = running;
condition.notify_all();
lock.unlock();
if ( wasRunning )
{
// wait for the async task to complete
task.wait();
}
}

private:
std::mutex mutex;
std::condition_variable condition;
std::chrono::seconds taskDelay;
std::chrono::steady_clock::time_point timeout;
std::queue< std::string > work;
std::future< void > task;
bool closing = false;
bool running = false;
std::chrono::steady_clock::time_point startTime;

void processWork()
{
std::unique_lock< std::mutex > lock( mutex );
// loop until std::chrono::steady_clock::now() > timeout
auto wait = timeout - std::chrono::steady_clock::now();
while ( !closing && wait > std::chrono::seconds( 0 ) )
{
condition.wait_for( lock, wait );
wait = timeout - std::chrono::steady_clock::now();
}
if ( !closing )
{
std::cout << "processing queue at " << std::chrono::duration_cast< std::chrono::milliseconds >( std::chrono::steady_clock::now() - startTime ).count() << "ms\n";
while ( !work.empty() )
{
std::cout << work.front() << "\n";
work.pop();
}
std::cout << std::flush;
}
else
{
std::cout << "aborting queue processing at " << std::chrono::duration_cast< std::chrono::milliseconds >( std::chrono::steady_clock::now() - startTime ).count() << "ms with " << work.size() << " remaining items\n";
}
running = false;
}
};

int main()
{
Batcher batcher;
batcher.queue( "test 1" );
std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
batcher.queue( "test 2" );
std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
batcher.queue( "test 3" );
std::this_thread::sleep_for( std::chrono::seconds( 2 ) );
batcher.queue( "test 4" );
std::this_thread::sleep_for( std::chrono::seconds( 5 ) );
batcher.queue( "test 5" );
}

Long precedure in timer's callback

The problem of long duration tasks interleaved with short duration tasks is a fundamental problem of real-time (and near-real-time) systems. Having a separate thread to handle longer running tasks, and delegating longer tasks to a second thread is a good solution. But is your long-running task waiting on io, or just heavy processing?

Should the delay be caused by asynchronous processing (waiting on io, for example), You might split the work into a 'top-half' and a 'bottom-half', where the 'top-half' dispatches the io request, and then schedules a 'bottom-half' to check for the results. Alternatives are to handle the signal from the response, possibly with a promise (really interesting technique).

Should the delay be cause by a processing heavy task, you might still be able to use a similar technique, by forming your compute heavy task in such a way that it 'yields' periodically. Threading is better, but may not be available in some embedded environments.



Related Topics



Leave a reply



Submit