Replacement for runDispatchLoopUntil()?

I am updating my code to the new async paradigm.

What can be used as a replacement for runDispatchLoopUntil() in the context shown below?

void runTaskWithProgressBar(te::ThreadPoolJobWithProgress& t) override
{
	double progress{ 0.0 };
	TaskRunner runner(t, progress);

	AlertWindow w("Rendering", {}, AlertWindow::NoIcon);
	w.addProgressBarComponent(progress);
	w.setVisible(true);

	while (runner.isThreadRunning())
		if (!MessageManager::getInstance()->runDispatchLoopUntil(10))
			break;
}

Bump

There isn’t a straightforward replacement. You would need to implement the code with class members and a Timer.

Have you looked at this? :

https://docs.juce.com/develop/classThreadWithProgressWindow.html

Since runDispatchLoopUntil() is not in the class anymore, I was hoping there would be some example code somewhere that would show what to use as an alternative.

To be honest, it is unclear to me how to achieve the same functionality. Any hints or suggestions will be most welcome!

Thank you!

I edited my post above to mention Juce’s ThreadWithProgressWindow. However, even when using that, you can’t just have a local instance of the class in some stand alone function. The object needs to stay alive somewhere else for the duration of the thread.

@reuk pointed me thankfully in the discord to Component::enterModalState().

Here is my implementation of an AlertWindow:

I have the code for AlertWindow sorted. It is specifically a replacement for the runDispatchLoopUntil() that I am unsure of.

In other words, how does one replace the following code?

		while (runner.isThreadRunning())
			if (!MessageManager::getInstance()->runDispatchLoopUntil(10))
				break;

Any suggestions are most welcome?

Thank you!

This loop is blocking. Instead you call enterModalState() and move the code that should happen on returning into the lambda.
The main difference is that you need the AlertWindow as member variable.

void runTaskWithProgressBar(te::ThreadPoolJobWithProgress& t) override
{
	double progress{ 0.0 };
	TaskRunner runner(t, progress);

	window = std::make_unique<AlertWindow> ("Rendering", {}, AlertWindow::NoIcon);
	window->addProgressBarComponent(progress);
	window->setVisible(true);

        window->enterModalState (true,
                                 juce::ModalCallbackFunction::create ([this] (int result)
        {
            window.reset();
        }));
}

And the TaskRunner should probably also be a member that can call window.reset() when it finishes.

You are consistently one of the most helpful people on this forum!

I will try this code and report back.

Thank you so much!

1 Like

I’m still fighting this issue. I will restate the problem below.

The original code displays an AlertWindow with a progress bar that increments as the render progresses. When the render completes the AlertWindow goes away and another AlertWindow shows the render success/fail status.

Below is the original code, using runDispatchLookUntil(). This code works as expected.

void runTaskWithProgressBar(te::ThreadPoolJobWithProgress& t) override
{
	double progress{ 0.0 };
	TaskRunner runner(t, progress);

	AlertWindow w("Rendering", {}, AlertWindow::NoIcon);
	w.addProgressBarComponent(progress);
	w.setVisible(true);

	while (runner.isThreadRunning())
		if (!MessageManager::getInstance()->runDispatchLoopUntil(10))
			break;
}
//=============================================================================
struct TaskRunner : public Thread
{
	TaskRunner(te::ThreadPoolJobWithProgress& t, double& prog) : Thread(t.getJobName()), task(t), progress(prog)
	{
		startThread();
	}

	~TaskRunner()
	{
		task.signalJobShouldExit();
		while(!waitForThreadToExit(0));
	}

	void run() override
	{
		while (!threadShouldExit())
		{
			progress = task.getCurrentTaskProgress();
			if (task.runJob() == ThreadPoolJob::jobHasFinished)
				break;
		}
	}

	te::ThreadPoolJobWithProgress& task;
	double& progress;
};

To move away from using runDispatchLoopUntil(), I have tried many things. The code below shows the AlertWindow with the progress bar, and it goes away when the render is complete. And, in fact, the render does complete! However, runTaskWithPrgressBar() never returns. So, the render status AlertWindow does not display until the DAW is being shut down.

void runTaskWithProgressBar(te::ThreadPoolJobWithProgress& t) override
{
	TaskRunner runner(t);
	MessageManager::getInstance()->runDispatchLoop();
}

//===========================================================================
struct TaskRunner : public Thread
{
	TaskRunner(te::ThreadPoolJobWithProgress& t) : Thread(t.getJobName()), task(t)
	{
		startThread();
	}

	~TaskRunner()
	{
		task.signalJobShouldExit();
		while(!waitForThreadToExit(10000));
	}

	void run() override
	{
		double progress{ 0.0 };
		AlertWindow w("Rendering", {}, AlertWindow::NoIcon);
		w.addProgressBarComponent(progress);
		w.setVisible(true);

		while (!threadShouldExit())
		{
			progress = task.getCurrentTaskProgress();
			if (task.runJob() == ThreadPoolJob::jobHasFinished)
				break;
		}
	}
	te::ThreadPoolJobWithProgress& task;
};

Any ideas are most welcome!

Thank you!

Regarding runDispatchLoop, the documentation says :

Also, I am a bit surprised it works to create and show the AlertWindow in your thread’s run() method. Generally, the Juce GUI objects are not designed to be used in other threads than the GUI/main thread.

Yes, I saw the note about using runDispatchLoop() on application startup. In my limited knowledge, I did not know of another way to initiate the thread. How would the thread be launched?

I moved the AlertWindow to the thread’s run() method in an attempt to simplify everything. I realize that is probably sketchy. It can easily be moved back to runTaskWithProgressBar().

I am sure this has a relatively simple solution. I have tried a number of things, but I do not have sufficient knowledge of threads to find the right code.

Maybe something like this would work:

  • Make your AlertWindow and progress into class data members, to ensure that they will live at least as long as the long-running task.
  • When job is ready to start, call enterModalState on the alert window, and set the progress to zero, all from the message thread.
  • Run the task on a background thread, querying the progress periodically. You can use MessageManager::callAsync to update the progress variable on the main thread.
  • Once the task is complete, you can use MessageManager::callAsync again to signal that the job is done, and launch a new AlertWindow.

Your main problem seems to be that you apparently want to have a stand alone function (the runTaskWithProgressBar) that would handle everything locally inside the function. But that’s not really feasible to do when following the new recommended async/non-modal model in Juce. You need to have some object that holds both the AlertWindow and the thread for the duration of the thread task. (You could maybe hack together something with heap allocations and self-destructing objects in order to run it all within a stand alone function, but that feels a bit iffy to do…)

Taking one step back I wonder why you are not using ThreadWithProgressWindow?

Although it should be noted the example shows the undesired blocking version. Instead launchThread() should be preferred.

EDIT: scrolling back I see that @xenakios suggested that already but without a response. Sorry for re-posting

I have been attempting to find a solution using ThreadWithProgressWindow. But again, my lack of knowledge is resulting in no luck.

void runTaskWithProgressBar(te::ThreadPoolJobWithProgress& t) override
{
	TaskRunner taskRunner{ t };
	taskRunner.launchThread();
}

//==============================================================================
struct TaskRunner : public ThreadWithProgressWindow
{
	TaskRunner(te::ThreadPoolJobWithProgress& t) : ThreadWithProgressWindow(t.getJobName(), true, false), task(t)
	{
		startThread();
	}

	~TaskRunner()
	{
		task.signalJobShouldExit();
		while (waitForThreadToExit(10000));
	}

	void run() override
	{
		while(!threadShouldExit())
		{
			setProgress(task.getCurrentTaskProgress());
			if (task.runJob() == ThreadPoolJob::jobHasFinished)
				break;
		}
	}
	te::ThreadPoolJobWithProgress& task;
};

BTW, this is all called from the TracktionEngine render function;

if (te::Renderer::renderToFile("Render", renderFile, *edit, range, tracksToDo, true, {}, true))
	AlertWindow::showMessageBoxAsync(AlertWindow::InfoIcon, "Rendered", renderFile.getFullPathName());
else
	AlertWindow::showMessageBoxAsync(AlertWindow::WarningIcon, "Render", "Failed!");

The current symptoms are;

  1. The render progress AlertWindow appears, but remains at 0%.
  2. The render does not happen.

I would expect the call to task.runJob() to make the render proceed. But it does not.

I have much to learn on all of this, so any help is most appreciated!

Well, similar to another code snippet you posted before, the main issue is that your TaskRunner is declared locally in a method that returns immediately (on purpose). In that moment the TaskRunner will be destroyed and the Task is terminated.

The TaskRunner stops the task in the destructor, that’s why you don’t see progress.

I don’t know why the window doesn’t immediately disappears though.

Try making the TaskRunner a member of the parent class.

I updated my code to the following;

void runTaskWithProgressBar(te::ThreadPoolJobWithProgress& t) override
{
	taskRunner = std::make_unique<TaskRunner>( t );
	taskRunner->launchThread();
}

//==============================================================================
struct TaskRunner : public ThreadWithProgressWindow
{
	TaskRunner(te::ThreadPoolJobWithProgress& t) : ThreadWithProgressWindow(t.getJobName(), true, false), task(t)
	{
		startThread();
	}

	~TaskRunner()
	{
		task.signalJobShouldExit();
		while (waitForThreadToExit(10000));
	}

	void run() override
	{
		while(!threadShouldExit())
		{
			setProgress(task.getCurrentTaskProgress());
			if (task.runJob() == ThreadPoolJob::jobHasFinished)
				break;
		}
	}
	te::ThreadPoolJobWithProgress& task;
};
std::unique_ptr<TaskRunner> taskRunner;

But, the AlertWindow appears briefly and then the DAW crashes!

The crash log might help.

But I realize you are using classes from the tracktion engine. I have no experience with that, e.g. te::ThreadPoolJobWithProgress.

The problem might not even be in this code snippet.

Below is the CallStack, if that is useful?

vcruntime140d.dll!memcpy_repmovs() Line 40
	at D:\agent\_work\61\s\src\vctools\crt\vcruntime\src\string\amd64\memcpy.asm(40)
[Inline Frame] TheDAW.exe!juce::ArrayBase<tracktion_engine::Clip *,juce::DummyCriticalSection>::addArrayInternal(tracktion_engine::Clip * const *) Line 406
	at C:\SDKs\JUCE\modules\juce_core\containers\juce_ArrayBase.h(406)
[Inline Frame] TheDAW.exe!juce::ArrayBase<tracktion_engine::Clip *,juce::DummyCriticalSection>::addArray(tracktion_engine::Clip * const *) Line 283
	at C:\SDKs\JUCE\modules\juce_core\containers\juce_ArrayBase.h(283)
TheDAW.exe!juce::Array<tracktion_engine::Clip *,juce::DummyCriticalSection,0>::Array<tracktion_engine::Clip *,juce::DummyCriticalSection,0>(const juce::Array<tracktion_engine::Clip *,juce::DummyCriticalSection,0> & other) Line 71
	at C:\SDKs\JUCE\modules\juce_core\containers\juce_Array.h(71)
TheDAW.exe!tracktion_engine::Renderer::Parameters::Parameters(const tracktion_engine::Renderer::Parameters & __that) Line 40
	at C:\SDKs\TracktionEngine\modules\tracktion_engine\model\export\tracktion_Renderer.h(40)
TheDAW.exe!tracktion_engine::Renderer::RenderTask::RendererContext::RendererContext(tracktion_engine::Renderer::RenderTask & owner_, tracktion_engine::Renderer::Parameters & p, tracktion_engine::AudioNode * n, juce::AudioFormatWriter::ThreadedWriter::IncomingDataReceiver * sourceToUpdate_) Line 191
	at C:\SDKs\TracktionEngine\modules\tracktion_engine\model\export\tracktion_Renderer.cpp(191)
[External Code]
TheDAW.exe!tracktion_engine::MessageThreadCallback::handleAsyncUpdate() Line 201
	at C:\SDKs\TracktionEngine\modules\tracktion_engine\utilities\tracktion_AsyncFunctionUtils.h(201)
[Inline Frame] TheDAW.exe!juce::InternalMessageQueue::dispatchMessage(juce::MessageManager::MessageBase *) Line 198
	at C:\SDKs\JUCE\modules\juce_events\native\juce_win32_Messaging.cpp(198)
TheDAW.exe!juce::InternalMessageQueue::dispatchMessages() Line 240
	at C:\SDKs\JUCE\modules\juce_events\native\juce_win32_Messaging.cpp(240)
TheDAW.exe!juce::InternalMessageQueue::dispatchNextMessage(bool returnIfNoPendingMessages) Line 124
	at C:\SDKs\JUCE\modules\juce_events\native\juce_win32_Messaging.cpp(124)
[Inline Frame] TheDAW.exe!juce::dispatchNextMessageOnSystemQueue(bool) Line 266
	at C:\SDKs\JUCE\modules\juce_events\native\juce_win32_Messaging.cpp(266)
[Inline Frame] TheDAW.exe!juce::MessageManager::runDispatchLoop() Line 107
	at C:\SDKs\JUCE\modules\juce_events\messages\juce_MessageManager.cpp(107)
[Inline Frame] TheDAW.exe!juce::JUCEApplicationBase::main() Line 262
	at C:\SDKs\JUCE\modules\juce_events\messages\juce_ApplicationBase.cpp(262)
TheDAW.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) Line 103
	at C:\AAXProjects2\TheDAW\Source\Main.cpp(103)
[External Code]