I am trying to use the same idea suggested as answer of my question in this post. Thi goal is to implement a class that will call 3 different methods with their own intervals inside a start_trigger method.
The following sample code is compiled successfully, but it does not work logically since the life time of IntervalTimer objects are limited to the start_process() method rather than class lifetime.
So my solution to resolve this issue is to define 3 variables of IntervalTimer as a class member and then initialize them inside the start_processing method.
The start_process method is a public method and can be called through an object of MyTestClass.
Here is the code that will be compiled with no issue, but it does not repeatedly call three methods (method1, method2, and method3 with the provided intervals).
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
#include <map>
#include <unordered_set>
#include <vector>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using duration = std::chrono::steady_clock::duration;
static constexpr auto now = std::chrono::steady_clock::now;
using boost::system::error_code;
using namespace std;
enum MethodTypes { FIRST = 0, SECOND = 1, THIRD = 2 };
using Callback = std::function<void()>;
//template<typename Callback>
struct IntervalTimer {
IntervalTimer(asio::any_io_executor ex, duration interval, Callback callback)
: interval_(interval)
, cb_(std::move(callback))
, tim_(ex, interval_)
{
loop();
}
private:
void loop() {
while (tim_.expiry() <= now())
tim_.expires_at(tim_.expiry() + interval_);
tim_.async_wait([this](boost::system::error_code const& ec) {
if (!ec) { // aborted or other failure
cb_();
loop();
}
});
}
duration interval_;
Callback cb_;
asio::steady_timer tim_;
};
class MyTestClass final {
public:
MyTestClass(int transaction_id , boost::asio::io_context& io) :
transaction_id_(transaction_id) ,io_context_(io) {
//tt1(clone( IntervalTimer()));
}
void start_process (vector<int>ids, vector<bool> conditions, map <int, unordered_set<string> >& set_of_items);
void stop_process();
private:
void method1 (int id, const unordered_set<string>& set_of_buyers);
void method2 (int id, const unordered_set<string>& set_of_sellers);
void method3 (int id, const unordered_set<string>& set_of_units);
void stats_cb(uint32_t tp_id, MethodTypes method_type, unordered_set<string>& set_of_values);
int transaction_id_;
boost::asio::io_context& io_context_;
//IntervalTimer tt1;
//IntervalTimer tt2;
//IntervalTimer tt3;
};
void MyTestClass::start_process(vector<int>ids, vector<bool> conditions, map <int, unordered_set<string> >& set_of_items){
if (conditions[0]) {
auto sample_interval = 10;
//IntervalTimer tt1_tmp (io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
// stats_cb(ids[0], MethodTypes::FIRST, set_of_items[0]);
//});
//tt1 =std::move(tt1_tmp);
IntervalTimer t1(io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
stats_cb(ids[0], MethodTypes::FIRST, set_of_items[0]);
});
}
if (conditions[1]) {
auto sample_interval = 20;
IntervalTimer t2(io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
stats_cb(ids[1], MethodTypes::SECOND, set_of_items[1]);
});
}
if (conditions[1]) {
auto sample_interval = 30;
IntervalTimer t3(io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
stats_cb(ids[2], MethodTypes::THIRD, set_of_items[3]);
});
}
io_context_.run();
}
void MyTestClass::stats_cb(uint32_t id, MethodTypes method_type, unordered_set<string>& set_of_values){
switch (method_type) {
case MethodTypes::FIRST:
method1(id, set_of_values);
break;
case MethodTypes::SECOND:
method2(id, set_of_values);
break;
case MethodTypes::THIRD:
method3(id, set_of_values);
break;
default:
cout<<"the provided type is not supported"<< endl;
return;
}
}
void MyTestClass::method1 (int v, const unordered_set<string>& set_of_values){
cout<<"method-1 is called and v ="<<v<<endl;
for (auto e: set_of_values)
cout<<e<< endl;
cout<<"method-1 is done"<<endl;
}
void MyTestClass::method2 (int v, const unordered_set<string>& set_of_values){
cout<<"method-2 is called and v ="<<v<<endl;
for (auto e: set_of_values)
cout<<e<< endl;
cout<<"method-2 is done"<<endl;
}
void MyTestClass::method3 (int v, const unordered_set<string>& set_of_values){
cout<<"method-3 is called and v ="<<v<<endl;
for (auto e: set_of_values)
cout<<e<< endl;
cout<<"method-3 is done"<<endl;
}
int main() {
boost::asio::io_context iocx;
MyTestClass obj(100, iocx);
vector<int> ids {100,200,300};
vector<bool> conds {false,true,true};
map <int, unordered_set<string> > mp;
mp[0].insert("test1_0");
mp[0].insert("test1_1");
mp[0].insert("test1_2"); mp[1].insert("test2_0");
mp[1].insert("test2_1");
mp[1].insert("test2_2"); mp[2].insert("test3_0");
mp[2].insert("test3_1");
obj.start_process(ids, conds, mp);
return 0;
}
However, when i uncomment the commented parts like the following definition inside the MyTestClass
//IntervalTimer tt1;
and the following one inside the start_process()
//IntervalTimer tt1_tmp (io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
// stats_cb(ids[0], MethodTypes::FIRST, set_of_items[0]);
//});
//tt1 =std::move(tt1_tmp);
I get the following compilation errors:
prog.cc: In constructor 'MyTestClass::MyTestClass(int, boost::asio::io_context&)':
prog.cc:53:56: error: no matching function for call to 'IntervalTimer::IntervalTimer()'
53 | transaction_id_(transaction_id) ,io_context_(io) {
| ^
prog.cc:23:5: note: candidate: 'IntervalTimer::IntervalTimer(boost::asio::any_io_executor, duration, Callback)'
23 | IntervalTimer(asio::any_io_executor ex, duration interval, Callback callback)
| ^~~~~~~~~~~~~
prog.cc:23:5: note: candidate expects 3 arguments, 0 provided
prog.cc:22:8: note: candidate: 'IntervalTimer::IntervalTimer(IntervalTimer&&)'
22 | struct IntervalTimer {
| ^~~~~~~~~~~~~
prog.cc:22:8: note: candidate expects 1 argument, 0 provided
Is there any suggestion how to resolve this error?
You can do anything to dynamically construct the IntervalTimer
objects. E.g. have a member like
std::list<IntervalTimer> timers;
Then, fixing some copy paste errors as well (conditions[1]
is repeated and set_of_items[3]
is always the empty set...):
void MyTestClass::start_process(std::vector<int> ids, std::vector<bool> conditions,
std::map<int, Set>& set_of_items) {
auto ex = io_context_.get_executor();
if (conditions[0])
timers.emplace_back(ex, 10ms, [&] { stats_cb(ids[0], MethodTypes::FIRST, set_of_items[0]); });
if (conditions[1])
timers.emplace_back(ex, 20ms, [&] { stats_cb(ids[1], MethodTypes::SECOND, set_of_items[1]); });
if (conditions[2])
timers.emplace_back(ex, 30ms, [&] { stats_cb(ids[2], MethodTypes::THIRD, set_of_items[2]); });
}
There's a big problem that you capture the local ids
by reference. This leads to Undefined Behavior. See below.
In general, simplify your code by reducing duplication. There was a LOT of copy/paste mistakes. Also, never using namespace std.
Always use good names for types as well as objects.
When you keep having the same code switching on an index (0,1,2), consider grouping the behavior and data in a class. Don't add useless repetition/indirection like stats_cb
, instead just write:
void MyTestClass::start_process(std::vector<int> ids, std::vector<bool> conditions,
std::map<int, Items>& set_of_items) {
if (conditions[0])
timers.emplace_back(ex_, 10ms, [this, id = ids[0], &items = set_of_items[0]] { method1(id, items); });
if (conditions[1])
timers.emplace_back(ex_, 20ms, [this, id = ids[1], &items = set_of_items[0]] { method2(id, items); });
if (conditions[2])
timers.emplace_back(ex_, 30ms, [this, id = ids[2], &items = set_of_items[0]] { method3(id, items); });
}
Do not call run
inside start_process
. MyTestClass doesn't own the io_context (and the name start_process
would be wrong, because it also completes the process).
Throwing in stop_process
:
void MyTestClass::stop_process() {
for (auto& t : timers)
t.cancel();
}
We get
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <unordered_set>
#include <vector>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using duration = std::chrono::steady_clock::duration;
static constexpr auto now = std::chrono::steady_clock::now;
using boost::system::error_code;
static auto const start = now();
static inline void trace(auto const&... msg) {
((std::cout << (now() - start) / 1ms << "\tms\t") << ... << msg) << std::endl;
}
struct IntervalTimer {
using Callback = std::function<void()>;
IntervalTimer(asio::any_io_executor ex, duration interval, Callback callback)
: interval_(interval)
, cb_(std::move(callback))
, tim_(ex, interval_) {
loop();
}
void cancel() {
post(tim_.get_executor(), [this] { tim_.cancel(); });
}
private:
void loop() {
while (tim_.expiry() <= now())
tim_.expires_at(tim_.expiry() + interval_);
tim_.async_wait([this](error_code const& ec) {
if (!ec) { // aborted or other failure
cb_();
loop();
}
});
}
duration interval_;
Callback cb_;
asio::steady_timer tim_;
};
using Items = std::unordered_set<std::string>;
class MyTestClass final {
public:
MyTestClass(int transaction_id, asio::any_io_executor ex) : transaction_id_(transaction_id), ex_(ex) {}
void start_process(std::vector<int> ids, std::vector<bool> conditions,
std::map<int, std::unordered_set<std::string>>& set_of_items);
void stop_process();
private:
void method1(int id, Items const& set_of_buyers);
void method2(int id, Items const& set_of_sellers);
void method3(int id, Items const& set_of_units);
int transaction_id_; // UNUSED
asio::any_io_executor ex_;
std::list<IntervalTimer> timers;
};
void MyTestClass::start_process(std::vector<int> ids, std::vector<bool> conditions,
std::map<int, Items>& set_of_items) {
// for (int id = 0; id < 3; ++id)
// timers.emplace_back(ex_, 10ms, [id, &items = set_of_items[id]] { method1(id, items); });
if (conditions[0])
timers.emplace_back(ex_, 1000ms, [this, id = ids[0], &items = set_of_items[0]] { method1(id, items); });
if (conditions[1])
timers.emplace_back(ex_, 2000ms, [this, id = ids[1], &items = set_of_items[0]] { method2(id, items); });
if (conditions[2])
timers.emplace_back(ex_, 3000ms, [this, id = ids[2], &items = set_of_items[0]] { method3(id, items); });
}
void MyTestClass::stop_process() {
for (auto& t : timers)
t.cancel();
}
void MyTestClass::method1(int v, Items const& set_of_values) {
trace("method-1", " is called and v =" , v );
std::cout << "\t\tmethod-1" << " iterating:";
for (auto const& e : set_of_values)
std::cout << " " << e;
std::cout << std::endl;
trace("method-1", " is done");
}
void MyTestClass::method2(int v, Items const& set_of_values) {
trace("method-2", " is called and v =" , v );
std::cout << "\t\tmethod-2" << " iterating:";
for (auto const& e : set_of_values)
std::cout << " " << e;
std::cout << std::endl;
trace("method-2", " is done");
}
void MyTestClass::method3(int v, std::unordered_set<std::string> const& set_of_values) {
trace("method-3", " is called and v =" , v );
std::cout << "\t\tmethod-3" << " iterating:";
for (auto const& e : set_of_values)
std::cout << " " << e;
std::cout << std::endl;
trace("method-3", " is done");
}
int main() {
asio::io_context ioc;
MyTestClass obj(100, ioc.get_executor());
std::map<int, Items> mp = {{0, {"test1_0", "test1_1", "test1_2"}},
{1, {"test2_0", "test2_1", "test2_2"}},
{2, {"test3_0", "test3_1"}}};
obj.start_process({100, 200, 300}, {false, true, true}, mp);
ioc.run_for(8s);
obj.stop_process();
ioc.run(); // graceful shutdown
}
Printing
2000 ms method-2 is called and v =200
method-2 iterating: test1_2 test1_1 test1_0
2007 ms method-2 is done
3000 ms method-3 is called and v =300
method-3 iterating: test1_2 test1_1 test1_0
3000 ms method-3 is done
4000 ms method-2 is called and v =200
method-2 iterating: test1_2 test1_1 test1_0
4000 ms method-2 is done
6000 ms method-2 is called and v =200
method-2 iterating: test1_2 test1_1 test1_0
6000 ms method-2 is done
6000 ms method-3 is called and v =300
method-3 iterating: test1_2 test1_1 test1_0
6000 ms method-3 is done
8000 ms method-2 is called and v =200
method-2 iterating: test1_2 test1_1 test1_0
8000 ms method-2 is done
Note that I made the delays much longer just so you can see the output. You can adjust them back to 10ms, 20ms, 30ms.
Always group objects and data. The repeated switching on indexes 0,1,2 indicates that something wants to be an entity:
struct Task {
using Func = std::function<void(int, Items const&)>;
bool condition = false;
duration interval;
Func method;
};
If you squint a little you will see that MyTestClass
really only adds complications at this point. It looks like a class that takes on too many responsibilities, ironically preventing the entities that need to exist from existing. How about this version:
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <unordered_set>
#include <vector>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using duration = std::chrono::steady_clock::duration;
static constexpr auto now = std::chrono::steady_clock::now;
using boost::system::error_code;
static auto const start = now();
static inline void trace(auto const&... msg) {
((std::cout << (now() - start) / 1ms << "\tms\t") << ... << msg) << std::endl;
}
struct RecurringTask {
using Callback = std::function<void()>;
RecurringTask(asio::any_io_executor ex, duration interval, Callback callback)
: interval_(interval)
, cb_(std::move(callback))
, tim_(ex, interval_) {
loop();
}
void cancel() {
post(tim_.get_executor(), [this] { tim_.cancel(); });
}
private:
void loop() {
while (tim_.expiry() <= now())
tim_.expires_at(tim_.expiry() + interval_);
tim_.async_wait([this](error_code const& ec) {
if (!ec) { // aborted or other failure
cb_();
loop();
}
});
}
duration interval_;
Callback cb_;
asio::steady_timer tim_;
};
using Items = std::unordered_set<std::string>;
void method(std::string_view name, int v, Items const& set_of_values) {
trace(name, " is called and v = ", v);
std::cout << "\t\t" << name << " iterating:";
for (auto const& e : set_of_values)
std::cout << " " << e;
std::cout << std::endl;
trace(name, " is done");
}
int main() {
asio::io_context ioc;
int transaction_id_ = 100; // UNUSED
Items buyers = {"test1_0", "test1_1", "test1_2"};
Items sellers = {"test2_0", "test2_1", "test2_2"};
Items units = {"test3_0", "test3_1"};
std::list<RecurringTask> tasks;
// tasks.emplace_back(ioc.get_executor(), 1000ms, bind(method, "buyers", 100, std::ref(buyers)));
tasks.emplace_back(ioc.get_executor(), 2000ms, bind(method, "sellers", 200, std::ref(sellers)));
tasks.emplace_back(ioc.get_executor(), 3000ms, bind(method, "units", 300, std::ref(units)));
ioc.run_for(8s);
}
Printing
2000 ms sellers is called and v = 200
sellers iterating: test2_2 test2_1 test2_0
2016 ms sellers is done
3000 ms units is called and v = 300
units iterating: test3_1 test3_0
3000 ms units is done
4000 ms sellers is called and v = 200
sellers iterating: test2_2 test2_1 test2_0
4000 ms sellers is done
6000 ms sellers is called and v = 200
sellers iterating: test2_2 test2_1 test2_0
6000 ms sellers is done
6000 ms units is called and v = 300
units iterating: test3_1 test3_0
6000 ms units is done
8000 ms sellers is called and v = 200
sellers iterating: test2_2 test2_1 test2_0
8006 ms sellers is done