FileChooser on stack vs launchAsync

What is roughly the difference between using FileChooser on stack vs using launchAsync mechanism?
Is the main event loop stalled while a modal is on screen?
It is rather new for me, and it doesn’t appears to be obvious while quickly reading the sources.

Putting it on the stack will start a new event loop at the beginning of the call. Although this is convenient, it can cause issues in plugins, as important messages (like quit) will now be processed inside the plugin, rather than from the host’s main event loop. This can result in unexpected behaviour, including crashes.

launchAsync doesn’t suffer from this problem, and it should be preferred in new projects.

1 Like

launchAsync doesn’t suffer from this problem, and it should be preferred in new projects.

That was i thought but the documentation and tutorials mostly use the other.

Putting it on the stack will start a new event loop at the beginning of the call.

That means that launchAsync doesn’t, and lets the main loop handles events?

Yes, we need to spend some time updating these. In a future release of JUCE, we’re considering disabling modal loops altogether in new projects (although we’ll still allow them to be enabled with a switch), so we’ll definitely update the examples/docs at that point, if not before.

Yes, exactly.

3 Likes

I’m finding it hard to manage the lifetime of the FileChooser with the launchAsync command. My current setup works like this, and it works on macOS, but on Windows my app hangs when ‘cancel’ is clicked. It works fine when a file is chosen, though.

auto fc = std::make_shared<juce::FileChooser> ("Choose", "C:", "*", true);
int flags = juce::FileBrowserComponent::canSelectFiles |= juce::FileBrowserComponent::openMode;

fc->launchAsync (flags, [fc] (juce::FileChooser& fc) {
    if (auto f = fc->getResult(); f.exists())
        doSomething (f);
});

If I don’t capture the shared_ptr into the callback, the dialog never appears (or is immediately cancelled).

edit: Ah, the commit mentioned at the end of this thread fixes the problem.

I do that in my code (with fileChooser_ a unique_ptr member of SpaghettisInstance class) :

void SpaghettisInstance::openPatch()
{
    fileChooser_ = std::make_unique<juce::FileChooser> (NEEDS_TRANS ("Choose a Patch to open..."),
                                currentOpenDirectory_,
                                spaghettis::core::getFileExtensions());
    
    int flags = juce::FileBrowserComponent::canSelectMultipleItems
                        | juce::FileBrowserComponent::openMode
                        | juce::FileBrowserComponent::canSelectFiles;
        
    auto callback = [] (const juce::FileChooser& fileChooser)
    {
        auto files = fileChooser.getResults(); for (const auto& f : files) { Spaghettis()->openPatch (f); }
    };
                        
    fileChooser_->launchAsync (flags, callback);
}

@widdershins

  1. It is a bit disturbing to have the same name fc for the parameter and the capture of your lambda.
  2. You are using a shared_ptr to an object copy captured in a lambda, then you pass this lambda to a method of the pointed object. Don’t know in that case but isn’t there a risk of memory leak due to self-referencing? < Lambda + shared_ptr = memory leak – floating.io >
  1. Yep, you’re right that’s a typo. In my actual code I have deleted the lambda argument’s name altogether.
  2. I will have to check if there’s a leak, but I don’t think there should be because the JUCE class nulls its asyncCallback in the finished method (just after the lambda is called). The leak mentioned in your link only happens if the callback is not deleted after it is called.

However, I do agree that what I’m doing feels somewhat brittle - if the JUCE implementation were different I might have a leak. It’s very convenient for me to have this in a free-standing function, but I might have to think about making an object that keeps a unique_ptr<FileChooser>, as you suggested. Thanks for your comments!

1 Like