Cockos Incorporated Forums

Cockos Incorporated Forums (https://forum.cockos.com/index.php)
-   REAPER Developer Forum (https://forum.cockos.com/forumdisplay.php?f=35)
-   -   JUCE GUI hangs when executing std::future (https://forum.cockos.com/showthread.php?t=189639)

SpiderFox 03-19-2017 02:26 PM

JUCE GUI hangs when executing std::future
 
I want to fetch data for a GUI.

I do this by calling std::future<std::vector<Project>> f = std::async(std::launch::async, &ProjectsController::getProjects, &pc) in the component which is intended to show the data.

Code:

ProjectBrowserComponent::ProjectBrowserComponent() : m_btn("OK") {
        addAndMakeVisible(m_tabs);
        setSize(500, 600);
        initData();
}

void ProjectBrowserComponent::initData() {
        std::cout << "requesting projs";
        std::future<std::vector<Project>> f = std::async(std::launch::async, &ProjectsController::getProjects, &pc);
        std::cout << "requested projs";
        std::vector<Project> projs;
        if (isReady(f)) {
                projs = f.get();
                std::cout << "got projs";
        }
}

template<typename R>
bool isReady(std::future<R> const& f)
{
        return f.wait_for(std::chrono::seconds(-1)) == std::future_status::ready;
}

The data is provided by a method called ProjectsController::getProjects() which lets it's thread sleep to simulate a remote service call which takes some time.

Code:

std::vector<Project> ProjectsController::getProjects() {
        std::vector<Project> result;
        if(serviceClient != nullptr) {
                try
                {
                        std::cout << "controller requesting projs\n";
                        std::this_thread::sleep_for(std::chrono::seconds(5));
                        std::cout << "controller requested projs\n";
                        result = serviceClient->getAvailableProjects();
                }
                catch (const std::exception&)
                {

                }
        }
       
        return result;
}

However, the UI does only apper after the thread sleep time has elapsed.

How can I make it load in the background?

If you want to have a look at it you can setup the project as follows:

Source code https://github.com/CodingSpiderFox/ReaperDAWHub

Install the conan package manager as python package
Code:

pip install conan
Install the dependencies

Code:

conan install --build Boost --build zlib --build Catch --build rapidjson -s arch=x86 -s build_type=Release -s compiler="Visual Studio" -s compiler.runtime=MD -s compiler.version=14
The project can be built with Visual Studio 2015 in the Release|Win32 configuration. (For lazyness, the project is built to C:\Program Files (x86)\REAPER\Plugins) so make sure to start VS 2015 as administrator or change the output directories

Xenakios 03-19-2017 04:04 PM

Quote:

Originally Posted by SpiderFox (Post 1820608)

std::future<std::vector<Project>> f = std::async(std::launch::async, &ProjectsController::getProjects, &pc);

How can I make it load in the background?

You have to store the returned future from std::async somewhere else than a local variable. The destructor of the future variable (or I think in your case running the isReady function) will block the execution of the current thread, negating the use of the async operation if the future is a local variable in a function. So make it a class member variable or a global variable instead. You will of course have to do the ready checking somewhere else too, taking care you don't block the GUI thread from returning to the event loop as soon as possible. (Maybe you could use a Juce timer that periodically checks if the future is ready or not, but also there you have to use do the check very quickly and not wait for the future.)

This problem is universal to GUI frameworks, not specific to JUCE. You should not do any operations that block the execution of the GUI thread for a long time.

SpiderFox 03-19-2017 04:13 PM

Quote:

Originally Posted by Xenakios (Post 1820660)
The destructor of the future variable (or I think in your case running the isReady function) will block the execution of the current thread, negating the use of the async operation if the future is a local variable in a function.

I guess that's the case because local variables are deleted at the end of the function?

Xenakios 03-19-2017 04:25 PM

Quote:

Originally Posted by SpiderFox (Post 1820669)
I guess that's the case because local variables are deleted at the end of the function?

Right.

There's a better way to do execution of background tasks, I thought I had done a utility function for that but I can't find it at the moment...

Anyway the idea is something like this :

Code:

void runAsync(std::function<void(void)> task, std::function<void(void)> completionHandler)
{
  std::thread th([task,completionHandler]()
  {
    task();
    MessageManager::callAsync(completionHandler);
  });
  th.detach();
  // the thread object being destroyed here doesn't matter, detach() has persisted the thread
}

void MyComponent::test()
{
  auto task = [](){ Sleep(4000); };
  auto completion = [this](){ m_statuslabel.setText("Task finished!") };
  m_statuslabel.setText("Running task...");
  runAsync(task,completion);
}

That wasn't tested that it compiles and runs correctly, since I typed it here directly. The neat part in that code is that it uses the Juce MessageManager::callAsync function to do some task completion handling in the GUI thread. (One must not update/repaint etc the GUI from other threads.)

SpiderFox 03-19-2017 05:05 PM

Your first post helped me a lot, it works now.

Will look at the second later

SpiderFox 03-23-2017 04:06 PM

I messed it up again ... I wish I were better in C++ ... I mess it up all the time and it really drives me crazy ... could you please have a look why it hangs again?

What I wanted to achieve is to use the Model View Controller pattern and the Strategy pattern for the code which should fetch the data asynchronously.

The code went into RepeatedPollingProjectsStrategy.cpp which is used in ProjectEntryListController which then is finally used in the GUI.

I tried to initialize the variable in the protected section
The broken version is https://github.com/CodingSpiderFox/R...29486bb8ccd48c

Could you please help me?

Xenakios 03-23-2017 04:19 PM

Quote:

Originally Posted by SpiderFox (Post 1822443)
I messed it up again ... I wish I were better in C++ ... I mess it up all the time and it really drives me crazy ... could you please have a look why it hangs again?

Sorry, your project is so big to take a look at, that at the moment I can't give better advice than to be really careful that you don't do anything like sleeping, waiting, incorrect mutex locking, long/infinite loops and such in the GUI thread. The GUI thread must constantly return back to the GUI event loop as quickly as possible. (I didn't extensively look at the code yet, but it looks like your problem is about waiting for the std::future in the GUI thread.)

Another thing to take care of is to not use the JUCE GUI or Reaper API(*) functions from anything else but the GUI thread. They may appear to work at times when called from other threads but probably just lead to hangs and crashes or worse problems.

(*) Some of the Reaper API functions may be thread safe, but I believe most are not.


All times are GMT -7. The time now is 10:27 PM.

Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2017, vBulletin Solutions Inc.