Как я могу эффективно использовать обратные вызовы с boost :: progress_display?

Я хотел бы использовать обратные вызовы с boost :: progress_display. Во-первых, у меня есть класс Foo, с которого мне нужно транслировать события:

class Foo
{
public:
template <typename L>
void attachListener(L const &listener)
{
m_callback.connect(listener);
}

void doWork()
{
for(...stuff...) {
m_callback(m_someData);
}
}
private:
Data m_someData;
boost::signals2::signal<void(Data const&)> m_callback;

}

Затем в каком-то другом коде у меня есть обратный вызов, который используется для обработки событий, поступающих из Foo. Затем я создаю Foo в otherFunction и прикрепляю к нему обратный вызов:

void callback(Data const &someData, boost::progress_display &pd)
{
// other stuff
++pd;
}

void otherFunction()
{
boost::progress_display pd(100);
boost::function<void(Data const&)> f(boost::bind(&callback, _1, boost::ref(pd)));
Foo foo;
foo.attachListener(f);
foo.doWork();
}

Когда вышеприведенное будет выполнено, обратный вызов будет вызван из Foo :: doWork.

Это правильный способ использовать обратные вызовы в сочетании с boost :: progress_display?

Это может раздражать, когда мне нужно создавать и подключать несколько обработчиков, каждый со своим собственным boost :: progress_display. Это будет означать, что каждая отдельная процентная полоса progress_display выводится одна за другой.

Спасибо,
Бен.

1

Решение

ОБНОВИТЬ В ответ на комментарий, вот простая реализация progress_group а также group_progress_display классы, которые вместе позволяют легко отображать один индикатор выполнения для нескольких различных элементов прогресса (progress_group::item экземпляры).

Видеть это Жить на Колиру.

Давайте посмотрим на progress_group:

struct progress_group {
struct item {
size_t current, total;

item(size_t total=100, size_t current=0)
: current(current), total(total) { }

void tick() {
if (current < total) current++;
}
};

std::list<boost::weak_ptr<progress_group::item> > members;

void add(boost::shared_ptr<item> const& pi) {
assert(pi);
members.push_back(pi);
}

item get_cumulative() {
item cumul(0, 0);

for(auto& wpi : members) {
auto pi = wpi.lock();

if (pi) {
cumul.current += pi->current;
cumul.total   += pi->total;
}
}

return cumul;
}
};

Обратите внимание, что я (произвольно) решил использовать слабые указатели, чтобы элементы прогресса могли исчезнуть, а масштаб будет просто отрегулирован.


Фактический прогресс динамично масштабируется до разрешения базового виджета дисплея (boost::progress_display). Основным ограничением, которое это оставляет, является то, что прогресс должен строго увеличиваться с течением времени (так как результат может быть не tty):

struct group_progress_display {

group_progress_display() : _display(1000), _reentrancy(0) {
}

void add(boost::shared_ptr<progress_group::item> pi) {
_group.add(pi);
}

void update() {
if (1 == ++_reentrancy) // cheap synch
{
auto cumul = _group.get_cumulative();

if (cumul.total > 0)
{
size_t target = (1.0 * cumul.current)/cumul.total * _display.expected_count();

if (target >= _display.count())
_display += target - _display.count();
}
}
--_reentrancy;
}
private:
boost::progress_display _display;
progress_group          _group;
boost::atomic_int       _reentrancy;
};

В этом образце выполняется 100 фоновых заданий с различными нагрузками в потоках и отображается один накопительный виджет прогресса:

int main()
{
boost::thread_group workers;
group_progress_display display;

for (int i = 0; i < 100; ++i)
{
auto load = (rand()%5) * 1500;
auto progress_item = boost::make_shared<progress_group::item>(load);
display.add(progress_item);

worker this_worker(progress_item->total);
this_worker.attachListener([=,&display]{
progress_item->tick();
display.update();
});

workers.create_thread(this_worker);
}

workers.join_all();
}

(Обратите внимание, как лямбда (или boost::bind выражение для c ++ 03) само поддерживает общий указатель.)

worker не знает ничего о реализации прогресса:

struct worker {
explicit worker(size_t count) : count(count) {}

template <typename L>
void attachListener(L&& listener) {
m_callback = std::forward<L>(listener);
}

void operator()()
{
for (size_t i = 0; i < count; ++i) {
boost::this_thread::sleep_for(boost::chrono::microseconds(500));
m_callback();
}
}

private:
boost::function<void()> m_callback;
size_t count;
};

Старый ответ

Это будет работать Жить на Колиру.

Потенциальная проблема заключается в том, что в данный момент индикатор прогресса должен каким-то образом иметь точную информацию о шагах процесса. Возможно, вы захотите скрыть эту информацию (внутри Data или обернуть ее в класс, который также добавляет информацию о ходе выполнения), а затем использовать простую арифметику, которую вы можете перевести в фиксированный масштаб (скажем, 100).

Это позволит даже ваш Foo::doWork настраивать масштаб на лету, что в значительной степени является доказательством того, что развязка работает.

1

Другие решения