Project

General

Profile

Updating from a background thread

Added by Anonymous over 12 years ago

I was thinking of using Wt for the web frontend to an existing C application that I have. I would like to have a "logging" console that shows the logging traffic from the application. I was trying to add a WText to a WContainerWidget similar to how the chat example does. When I do this from a boost::thread in the background, I get a segfault. Does someone have an example of something like this that I could study and look at?


Replies (12)

RE: Updating from a background thread - Added by Anonymous over 12 years ago

Ok, in answer to my own question, it looks like I need to look at WApplication::enableUpdates, WApplication::getUpdateLock, and WApplication::triggerUpdate. I'll try those out and see how it goes.

RE: Updating from a background thread - Added by Tony Rietwyk over 12 years ago

Sending a signal from the thread back to the main app might be simpler?

RE: Updating from a background thread - Added by Florian Ransmayr over 12 years ago

Hello Slide Earl,

I think there are two examples that might help you:

  • serverpush
  • broadcast
    If you are using the triggerUpdate function from a different thread as the main loop you need to take care of the UpdateLock. You can see how to handle this in the serverpush example. One really important thing is that you call Wt::WApplication::enableUpdate(true) to enable serverpushes in general.
    Another way that I prefere and have used in my project as well is the Wt::WServer::post method. Here you dont't need to handle the UpdateLock manually. This version is used in the broadcast example.

You can find a complete description of these two methods here:

http://www.webtoolkit.eu/wt/doc/reference/html/classWt_1_1WApplication.html#ad9631ca64e68d30d40cb49c90e55223d

I hope that helps.

Best Regards

Florian

RE: Updating from a background thread - Added by Michael Backus over 11 years ago

I'm also curious about too. I'm writing some code for a robot that uses OpenCV to track an object by color. I'm using Wt to provide a GUI for debugging purposes, but I also want to run the program without a GUI for performance reasons (I'm using a BeagleBone Black). The code below accomplishes this goal, but when the GUI is running, I have to use Wt's timer to trigger the generation of images that are displayed by the GUI. I'd like to trigger these updates by adding some code to the end of the trackCV function. I'm aware Wt's triggerUpdate or server::post features can be used to accomplish this goal, but I don't know how to use either method when calling from a separate thread. I am aware of the serverpush and broadcast examples that illustrate the use of these features, but they don't show how to trigger the update from an entirely different thread.

//Object used by setupCV & trackCV
VideoCapture capture(0);   

class ControlPanel : public WApplication
{
public:
    ControlPanel(const WEnvironment& env);

private:
    WImage *frame_;

    void timeout();
};

void setupCV()
{
    //Code to setup camera
}

void trackCV(bool gui)
{
  //Code to track blue object
  //Add code to trigger timeout here
}

void camera()
{
    setupCV();

    while(true)
        trackCV(false);
}

ControlPanel::ControlPanel(const WEnvironment& env)
    : WApplication(env)
{
    setTitle("Control Panel");

    frame_ = new WImage("frame.jpg",root());

    setupCV();

    WTimer *timer = new Wt::WTimer(root());
    timer->setInterval(50);
    timer->timeout().connect(this, &ControlPanel::timeout);
    timer->start();
}

void ControlPanel::timeout()
{
    trackCV(true);
    ostringstream filename;
    filename << "frame.jpg?" << rand() % 1000000;
    frame_->setImageLink(filename.str().c_str());
}

WApplication *createApplication(const WEnvironment& env)
{
    WApplication *app = new ControlPanel(env);
    return app;
}

int main(int argc, char **argv)
{
    bool gui = false;

  //Logic to alter gui depending on argv

    if(gui)
        return WRun(argc, argv, &createApplication);
    else
    {
        boost::thread cameraThread(camera);
        cameraThread.join();
        return(0);
    }
}

RE: Updating from a background thread - Added by Wim Dumon over 11 years ago

Hey,

In practice, this is what you need to do:

  • In camera(), invoke WServer::post() for each session to trigger the execution of the timeout() function each time you generated a new image. Post() will cause the timeout() function to be executed in the context of the proper session, so that you don't need to lock the widget tree.
  • In timeout(), add a call to WApplication::triggerUpdate()
  • In the ControlPanel constructor, remove the WTimer, and add a call to enableUpdates()

This turns your program in a server-push program - instead of polling from the client, new images will be pushed by the server to the client. See also the examples referenced by Florian. Especially the broadcast example, where a list of active sessions is maintained.

Best regards,

Wim.

RE: Updating from a background thread - Added by Wim Dumon over 11 years ago

And I forgot to mention WServer::postAll(), which eliminates the need to track all active sessions in your own code.

Best regards,

Wim.

RE: Updating from a background thread - Added by Michael Backus over 11 years ago

Conceptually, I understand what you're saying, but I don't know how to invoke WServer::postAll() from the camera() function. I've tried to do what you suggest, but I'm running into scoping issues. Here are the errors I get when I try to compile my code.

RoboBeagleBot.cpp:23:1: error: ‘Server’ does not name a type
RoboBeagleBot.cpp: In function ‘void trackCV(bool)’:
RoboBeagleBot.cpp:92:24: error: ‘class Wt::WServer’ has no member named ‘postAll’
RoboBeagleBot.cpp:92:32: error: ‘updateImage’ was not declared in this scope

Here's the code I've put together so far.

//Object used by setupCV & trackCV
VideoCapture capture(0);   

Server server;

class ControlPanel : public WApplication
{
public:
    ControlPanel(const WEnvironment& env);

private:
    WImage *frame_;

    void updateImage();
};

void setupCV()
{
    //Code to setup camera
}

void trackCV(bool gui)
{
  //Code to track blue object
  //Logic to change functionality depending on value of gui
    WServer::instance()->postAll(updateImage);
}

void camera(bool gui)
{
    setupCV();

    while(true)
        trackCV(gui);
}

ControlPanel::ControlPanel(const WEnvironment& env)
    : WApplication(env)
{
    setTitle("Control Panel");

    frame_ = new WImage("frame.jpg",root());

    WApplication::instance()->enableUpdates(true);  
}

void ControlPanel::updateImage()
{
    ostringstream filename;
    filename << "frame.jpg?" << rand() % 1000000;
    frame_->setImageLink(filename.str().c_str());
    WApplication::instance()->triggerUpdate();  
}

WApplication *createApplication(const WEnvironment& env)
{
    WApplication *app = new ControlPanel(env);
    return app;
}

int main(int argc, char **argv)
{
    bool gui = false;

  //Logic to alter gui depending on argv

    if(gui)
    {
        boost::thread cameraThread(camera,true);
        return WRun(argc, argv, &createApplication);
    }
    else
    {
        boost::thread cameraThread(camera,false);
        cameraThread.join();
        return(0);
    }
}

RE: Updating from a background thread - Added by Michael Backus over 11 years ago

I forgot to mention that I put the WServer::post line of code at the end of the trackCV function (that's the one that runs every time an image is captured).

RE: Updating from a background thread - Added by Wim Dumon over 11 years ago

RoboBeagleBot.cpp:23:1: error: ‘Server’ does not name a type

Remove line 23? Server is not a Wt class, I don't know what your intention is.

RoboBeagleBot.cpp: In function ‘void trackCV(bool)’:
RoboBeagleBot.cpp:92:24: error: ‘class Wt::WServer’ has no member named ‘postAll’

postAll() was only recently added. I believe you need the latest version of Wt (3.3.4-rc1)

RoboBeagleBot.cpp:92:32: error: ‘updateImage’ was not declared in this scope

This is slightly more complex. For starters, you should write &ControlPanel::updateImage. But that won't work, since updateImage is a member function. An easy way around this is to create a static function that will be used as callback. As this function will be executed in the context of a session, you can use wApp (short for WApplication::instance()) to obtain the pointer to the active WApplication and invoke updateImage from there.

Thus:

... (in the header file of ControlPanel, in the ControlPanel class declaration)
  static void staticUpdateImage();
...

... (in your thread)
  WServer::postAll(&ControlPanel::staticUpdateImage);
...

void ControlPanel::staticUpdateImage()
{
  ControlPanel *app = dynamic_cast<ControlPanel *>(WApplication::instance());
  if (app) {
    app->updateImage();
  } else {
    // this cannot really happen
  }
}

RE: Updating from a background thread - Added by Michael Backus over 11 years ago

Thanks for the reply. It looks like what you've provided is the missing piece. However, I'd like to try and get this example working with the current version of Wt that I'm using. I'm an educator working with students that will be using BeagleBone Blacks, so I'd like to stick with installing Wt via apt-get if possible.

Looks like the approach is very similar-the only wrench in the process is figuring out the session ID. How would I do that from the trackCV function?

Also, it looks like I might be able to eliminate the update image function altogether. My only question is, how would I run the

WApplication::instance()->triggerUpdate();

line of code from the staticUpdateImage function?

Would I just use

app->triggerUpdate();

RE: Updating from a background thread - Added by Michael Backus over 11 years ago

I've done my best to do as you have suggested with the exception of trying to use post instead of postAll (I tried updating Wt, but ran into some cmake issues that I don't really want to walk my students through if I don't have to). At any rate, I'm able to pass the session ID to trackCV, but I'm still getting the following compilation error:

RoboBeagleBot.cpp: In function ‘void trackCV(bool, std::string)’:
RoboBeagleBot.cpp:92:52: error: cannot call member function ‘void Wt::WServer::post(const string&, const boost::function<void()>&, const boost::function<void()>&)’ without object
make: *** [RoboBeagleBot] Error 1

The code that generated the error is below.

//Object used by setupCV & trackCV
VideoCapture capture(0);   

class ControlPanel : public WApplication
{
public:
    ControlPanel(const WEnvironment& env);

    static void staticUpdateImage();

private:
    WImage *frame_;

    void updateImage();
};

void setupCV()
{
  //Code to setup camera
}

void trackCV(bool gui,const string id)
{
  //Code to track blue object

    if(gui)
    {
        imwrite("/home/debian/robot/frame.jpg",frame);
        WServer::post(id,&ControlPanel::staticUpdateImage);
    }
    else
        cout << "x: " << x << "\t" << "y: " << y << endl;
}

void camera(bool gui,const string id)
{
    setupCV();

    while(true)
        trackCV(gui,id);
}

ControlPanel::ControlPanel(const WEnvironment& env)
    : WApplication(env)
{
    setTitle("Control Panel");

    frame_ = new WImage("frame.jpg",root());

    WApplication::instance()->enableUpdates(true);  
}

void ControlPanel::updateImage()
{
    ostringstream filename;
    filename << "frame.jpg?" << rand() % 1000000;
    frame_->setImageLink(filename.str().c_str());
    WApplication::instance()->triggerUpdate();  
}

void ControlPanel::staticUpdateImage()
{
  ControlPanel *app = dynamic_cast<ControlPanel *>(WApplication::instance());
  if (app) {
    app->updateImage();
  } else {
    // this cannot really happen
  }
}

WApplication *createApplication(const WEnvironment& env)
{
    WApplication *app = new ControlPanel(env);
    string id;
    id = app->sessionId();
    boost::thread cameraThread(camera,true,id);
    return app;
}

int main(int argc, char **argv)
{
    bool gui = false;

    for(int i = 0 ; i < argc ; i++)
    {
        //Use the http-port argument required by Wt to determine if a gui is needed
        if(string(argv[i]) == "--http-port")
            gui = true;
    }

    if(gui)
    {
        return WRun(argc, argv, &createApplication);
    }
    else
    {
        boost::thread cameraThread(camera,false,"NoGui");
        cameraThread.join();
        return(0);
    }
}

RE: Updating from a background thread - Added by Wim Dumon over 11 years ago

Post is a member function, and since WServer is a singleton, you can call:

WServer::instance()->post(...);

Wim.

    (1-12/12)