Project

General

Profile

Updating model and view in background

Added by Boris Grinac almost 6 years ago

Hello, I have question:

We are building an application on top of live database. So we need to update our model in background and we expect that the attached view will be updated as well. To update the model, we have created a new thread that does all the database job. After the thread reads the database, we have connected a signal that makes the model update.

Now have two problems:

  1. We are able to update the model, but the view is not updated automatically.
  2. Our thread is detached and runs in background, but when user clicks refresh in browser, a new session is started. Our thread is linked by its signal to the old session that is destroyed and so we have access violation - crash. To fix this, we can run the thread in global context of application, not in context of current session. But then the session should look into the status of the background task and ask for new updated data. So we need to run background task inside session anyway.

So the question is what is the best way to arrange our application so that we are able to update data in background?

Maybe someone can provide an example?


Replies (7)

RE: Updating model and view in background - Added by lm at almost 6 years ago

This is an excellent question, and I'm sorry I can't properly answer it. The feature you're looking for is special in web technologies and called "Server Push". For your edification:

https://en.wikipedia.org/wiki/Push_technology#HTTP_server_push

I found this project by Roel that claims to demonstrate Wt's implementation of server push:

https://github.com/emweb/wt/tree/master/examples/feature/serverpush

It look like you'll be looking a Wt::WApplication::enableUpdates and Wt::WApplication::triggerUpdate. Good luck!

RE: Updating model and view in background - Added by Wim Dumon almost 6 years ago

Please also note that you can't just touch the widget tree from a thread without locking the widget tree. Wt does this locking automatically for browser-generated events, but for your own events you'll have to grab the WApplication::UpdateLock before touching the widget tree. An alternative is to post an event to your session through WServer::post().

Best regards,

Wim.

RE: Updating model and view in background - Added by Pedro Vicente over 5 years ago

I have a similar case: a map that needs to be updated say, every 1 second.

I tried to modify the serverpush example to not use a separate thread , but what happens is that the interface is only updated at the end (at the end of the cycle on the example; in my example I just add a new text widget every 1 second 3 times)

separate question: how to trigger the event automatically at application start ? (without the need for the push button event)

here's the code

class BigWorkWidget : public WContainerWidget
{
public:
  BigWorkWidget()
    : WContainerWidget()
  {
    Wt::WApplication *app = Wt::WApplication::instance();
    startButton_ = this->addWidget(cpp14::make_unique<WPushButton>("Start"));
    startButton_->clicked().connect(this, &BigWorkWidget::startBigWork);
  }

private:
  WPushButton *startButton_;

  void startBigWork() 
  {
    WApplication *app = WApplication::instance();
    app->enableUpdates(true);
    doBigWork(app);
  }

  void doBigWork(WApplication *app)
  {
    for (unsigned i = 0; i < 3; ++i) 
    {
      std::this_thread::sleep_for(std::chrono::milliseconds(1000));

      WApplication::UpdateLock uiLock(app);
      if (uiLock) 
      {
        addWidget(Wt::cpp14::make_unique<Wt::WText>("Plot  "));
        app->triggerUpdate();
      }
      else
        return;
    }
  }
};

//////////////////////////////////////////////////////////////////////////////////////
//Application_update
///////////////////////////////////////////////////////////////////////////////////////

class Application_update : public WApplication
{
public:
  Application_update(const WEnvironment& env) : WApplication(env)
  {
    std::unique_ptr<BigWorkWidget> update = cpp14::make_unique<BigWorkWidget>();
    root()->addWidget(std::move(update));
  }
};

RE: Updating model and view in background - Added by lm at over 5 years ago

I have yet to do anything which requires server push, but I would expect that you need to return from your big work function instead of looping there. If you want to add a widget every second (or do anything regularly like that), use Wt::WTimer: https://www.webtoolkit.eu/wt/doc/reference/html/classWt_1_1WTimer.html

If I understand correctly, this will not require server push since the client can poll the server at set intervals. Also, it will start whenever you start it (at application load time if you like) rather than requiring a button event initiated by the client.

RE: Updating model and view in background - Added by Wim Dumon over 5 years ago

Hey,

While doing time consuming work in the UI thread, you can render changes that happened until now (e.g. progress bar or intermediate updates) and react on pending user events (like the click on a cancel button that you may show during the long operation) by invoking WApplication::processEvents().

I'd recommend to use a server push mechanism rather than a WTimer for implementing server push.

Wim.

RE: Updating model and view in background - Added by Pedro Vicente over 5 years ago

Thanks, Wim

that worked, by adding that call in the above code

if (uiLock)
{
  addWidget(Wt::cpp14::make_unique<Wt::WText>("Plot  "));
  app->triggerUpdate();
  app->processEvents();
}

RE: Updating model and view in background - Added by Pedro Vicente over 5 years ago

I solved the problem for the push to start at application start by running a timer 1 time and make the push start there, then stop the timer.

I searched the examples for a case of a signal/slot that would allow to do this (start the push without a user event, like the start button)

but could not find any... if this is possible please let me know

///////////////////////////////////////////////////////////////////////////////////////
//PushWidget
///////////////////////////////////////////////////////////////////////////////////////

class PushWidget : public WContainerWidget
{
public:
  PushWidget()
    : WContainerWidget()
  {
    Wt::WApplication *app = Wt::WApplication::instance();
    startButton_ = this->addWidget(cpp14::make_unique<WPushButton>("Start"));
    startButton_->clicked().connect(this, &PushWidget::startBigWork);

    timer_ = cpp14::make_unique<WTimer>();
    timer_->setInterval(std::chrono::milliseconds{ 1000 });
    timer_->timeout().connect(this, &PushWidget::timerTick);
    timer_->start();
  }

private:
  WPushButton *startButton_;
  std::unique_ptr<WTimer> timer_;
  void timerTick()
  {
    timer_->stop();
    startBigWork();
  }

  void startBigWork()
  {
    WApplication *app = WApplication::instance();
    app->enableUpdates(true);
    doBigWork(app);
  }

  void doBigWork(WApplication *app)
  {
    for (unsigned i = 0; i < 5; ++i)
    {
      std::this_thread::sleep_for(std::chrono::milliseconds(2000));

      WApplication::UpdateLock uiLock(app);
      if (uiLock)
      {
        addWidget(Wt::cpp14::make_unique<Wt::WText>("Plot  "));
        app->triggerUpdate();
        app->processEvents();
      }
      else
        return;
    }
  }
};
    (1-7/7)