BackgroundTask
Every callback that you define in HiseScript is being serialised and called on a single thread in order to avoid synchronisation issues and race conditions (a situation where two threads try to access the same resource). There is a priority system in place which makes sure that certain tasks cannot block other tasks (so eg. a control callback will be executed before a paint routine and if you recompile a script it will discard all pending callbacks for this script processor).
A notable exception is of course the realtime callbacks which are executed directly in the audio thread but since you should not allocate anything in there anyways, any possible race conditions between the scripting thread and the audio thread are not super critical.
However there are a few occasions where this model creates unwanted side effects: a really complex task that will take a lot of time might clog this system and prevent user interaction. In order to solve this, you can use this object to offload a heavyweight function to a separate thread while the scripting thread remains idle and responsive.
Be aware that at this point it is a highly experimental function and I would not advise using this without extensive testing of your use case as there might be many subtle issues that come from the multithreading of the function executions.
The prime use case for this object is when you are working with samples to create custom workflow tools.
In order to use it, create an object with Engine.createBackgroundTask()
, and then give it a function to execute with callOnBackgroundThread()
.
Class methods
callOnBackgroundThread
Call a function on the background thread.
BackgroundTask.callOnBackgroundThread(var backgroundTaskFunction)
This will start a new thread and call the function that you've passed in. The function must have a single parameter which will contain a reference to this object that you can use for status reporting / thread control.
getProgress
Get the progress for this task. Edit on GitHub
BackgroundTask.getProgress()
getProperty
Retrieve a property through a thread safe container. Edit on GitHub
BackgroundTask.getProperty(String id)
getStatusMessage
Returns the current status message. Edit on GitHub
BackgroundTask.getStatusMessage()
killVoicesAndCall
Kills all voices and calls the given function on the sample loading thread. Edit on GitHub
BackgroundTask.killVoicesAndCall(var loadingFunction)
runProcess
Spawns a OS process and executes it with the given command line arguments.
BackgroundTask.runProcess(var command, var args, var logFunction)
This function will use a child process to run any command line application on this background thread. Just pass in the command you want to execute, the command line arguments (either as an array of strings or as a single string which will be split up at unquoted whitespace characters) and a logger function that will periodically read the programs output and called whenever a new line was printed to the standard output. The function needs to have three arguments which will contain:
- A reference to the background task object (so you can set the progress)
- a
bool
value that is true when the program has finished - An integer containing the exit code (if the program has finished and the second argument is
true
) or a string for each line from the program output.
Be aware that in order to allow a graceful exit, you have to set the timeout of this background task to be higher than the longest interval between the output of your program, otherwise the thread might be killed with undefined behaviour.
This example here uses CURL to download a 100MB test file and put it on the Desktop. It should work on both macOS and Windows (10).
// Create a background task and give it a name that's descriptive
const var b = Engine.createBackgroundTask("DownloadTest");
// Let's forward the status and progress to the official HISE progress system
// (this will show the status in the main top bar in HISE and you can hook up
// Panels to a preload callback on your UI)
b.setForwardStatusToLoadingThread(true);
// CURL will spit out a new line roughly every second, so setting the timeout to 2 seconds will ensure
// that it can be cancelled gracefully.
b.setTimeOut(2000);
// We can use the finish callback to show / hide some elements
b.setFinishCallback(function(isFinished, wasCancelled)
{
b.setProgress(0.0);
Console.print("Finished: " + isFinished);
Console.print("Cancelled: " + wasCancelled);
});
function downloadLogger(thread, isFinished, data)
{
if(isFinished)
return;
// Now let's hack around and format the CURL output to something
// that we can show in HISE
// Begin of nasty hacking procedure...
var v = data.split(" ");
for (i = 0; i < v.length; i++)
{
if (v[i].length == 0)
v.removeElement(i--);
}
var progress = parseInt(v[0]) / 100.0;
var m = "Downloading " + v[3] + "B of " + v[1] + "B";
if (progress < 0.01)
m = "Start Downloading...";
// ...End of nasty hacking procedure
thread.setProgress(progress);
thread.setStatusMessage(m);
}
// This button will just start and cancel the download
const var button = Content.addButton("Run", 0, 0);
button.set("saveInPreset", false);
inline function onButton(component, value)
{
if(value)
{
local f = FileSystem.getFolder(FileSystem.Desktop).getChildFile("testfile.dat");
// Use CURL to download a test file of 100MB
b.runProcess("curl", ["https://speed.hetzner.de/100MB.bin",
"--output",
f.toString(0)],
downloadLogger);
}
else
{
// This should gracefully cancel the download
b.sendAbortSignal(false);
}
};
Content.getComponent("Run").setControlCallback(onButton);
sendAbortSignal
Signal that this thread should exit.
BackgroundTask.sendAbortSignal(bool blockUntilStopped)
If the task is running, this will send a abort signal so that the next time you call shouldAbort()
it will return false and gives you the option to cancel the task gracefully.
This can be called from any thread (but the most useful application is a control callback obviously). if blockUntilStopped
is true, the function will wait until the thread has been stopped (in case you require that the thread has been terminated before proceeding).
setFinishCallback
Set a function that will be called when the task has started / stopped.
BackgroundTask.setFinishCallback(var newFinishCallback)
You can pass a function with two parameters here that will be executed when the thread starts and when it stops. This might be useful for notifying your UI that the task is in progress. Be aware that this function will be called on the scripting thread and might be executed at the same time as the actual task!
setForwardStatusToLoadingThread
Forward the state of this thread to the sample loading thread notification system.
BackgroundTask.setForwardStatusToLoadingThread(bool shouldForward)
There is another background thread in HISE that is used for preset / sample loading as well as for other heavyweight tasks. This offers a convenient notification system that you might already use in your project. In this case you can call this method with true
and it will forward any status changes of this task. This includes:
- ScriptPanel.setLoadingCallback() : will be called when the task starts / stops
- Engine.getPreloadMessage()
: will return the message passed in with
setStatusMessage()
- Engine.getPreloadProgress() : will return the task's progress
Be aware that this only captures the notification system for the time the task is active but does not lock the real loading thread so if you spawn a task on the main loading thread (eg. loading a samplemap) while your custom task is active, it might create glitches and inconsistent notifications.
setProgress
Set a progress for this task. Edit on GitHub
BackgroundTask.setProgress(double p)
setProperty
Set a property to a thread safe container. Edit on GitHub
BackgroundTask.setProperty(String id, var value)
setStatusMessage
Sets a status message. Edit on GitHub
BackgroundTask.setStatusMessage(String m)
setTimeOut
Set timeout.
BackgroundTask.setTimeOut(int ms)
You can set a custom timeout period that the thread will wait until it will force-kill the task (which might leave things in a bad place). The default time is 500 milliseconds, but you can change this value if you need. The best way to get an estimate for how long you need the timeout is the period between calls to shouldAbort()
. So if you have a task like this:
function myTask(thread)
{
for(i = 0; i < 1000000; i++)
{
if(thread.shouldAbort())
break;
subFunctionThatTakes900MillisecondsPerRun();
}
}
you should set the timeout to something like 1000ish milliseconds - including some headroom for computers that are slower obviously and I would rather suggest to break down the function in the loop into multiple parts and call shouldAbort()
more often to avoid any freezing.
shouldAbort
Checks whether the task should be aborted (either because of recompilation or when you called abort().
BackgroundTask.shouldAbort()
This function should be called as often as possible to figure out whether the thread needs to be cancelled, which might happen because of two reasons:
- You have called
sendAbortSignal()
- This object is being deleted (either because your app is terminated or the script is recompiled inside HISE)
If you're doing things in a loop it's highly advised to call it once per loop and the time between two calls to shouldAbort()
must never exceed the thread timeout (in fact the execution will time out if you fail to do so which is a safe check that should prevent you from forgetting to call this).