documentation

Using Commands and Tasks

Level: Intermediate

For controlling the behaviour of VisionLib during runtime, we provide a variety of different commands. A list of available commands can be found at Command Reference. In Unity we reflected these commands in the corresponding tracker components:

  • CameraCalibration
  • ImageRecorder
  • ModelTracker
  • MultiModelTracker
  • PosterTracker

Some commands will influence the execution of the current tracking algorithm. To not interfere with the current execution, every command sent to VisionLib will be stored and performed after the tracking of the current frame has been finished. So some time might elapse between sending a command and receiving a result.

To reflect this behaviour inside Unity, we are using the Task interface. This allows every developer to perform code after a command has successfully returned without complex registering of callback functions. It also allows the user to receive the result of a performed command and process it as soon as the asnychronous command has returned the result. This way, commands are easier to use and understand than implementing this with callback functions.

What are Tasks and how do they work

A Task in C# can be seen as an interface to a function processed in the background. You can copy or move the Task, without affecting its processing. If you Start a Task, it will be executed and usually performed in a thread of a thread pool.

A Task contains either a result (which might also be void) or an Exception (if the task is "canceled" it also contains an Exception). If you await a Task (see section below), the Task will return its result or rethrow its exception. So the Task asynchronous programming model (TAP) provides an abstraction over asynchronous code. You write code as a sequence of statements, just like always.

You can wait for a Task completion in two ways:

  • If you await a Task in an async function, the execution of the calling function continues (like a return for the current thread). After the Task has been completed, an arbitrary thread continues the execution inside the function after the await.
  • If you Wait on a Task object, the execution of the current thread will pause until the Task has been finished. We recommend to not use Wait since this might make your GUI freeze and lead to deadlocks in your program.

Using Commands in your scripts

If you want to use a command inside your program flow, you should start with an async Task function.

private async Task MyFunctionAsync() {}

Inside this function you can wait for a command to finish with the keyword await. The program flow inside this function will be synchronous:

private async Task MyFunctionAsync()
{
// <-- Code before this point is executed before calling 'ResetHard'
await ModelTrackerCommands.ResetHard(); // This command will take some time
// <-- Code after this point is executed after the 'ResetHard' call has finished
}

You can again use your new MyFunctionAsync in another async Task function to structure your code. But at some point you just want to process the task in the background. This is especially relevant, if you want to call a function from the Unity Editor or in the GUI. Therefore you have to add an additional function:

public void MyFunction()
{
TrackingManager.CatchCommandErrors(MyFunctionAsync());
}

Awaiting commands (with await) might throw either a WorkerCommands.CommandError exception (if the command could not be executed correctly) or a System.Threading.Task.TaskCanceledException (indicating, that the command has not been executed). Therefore we provide a helper function which takes care about those exceptions: TrackingManager.CatchCommandErrors. Calling this function will execute the MyFunctionAsync in the background and will send a notification if an error in the execution of a command occurred.

Important: At this point (Changing from Task to void functions), the execution order might not be as expected:

private async Task MyFunctionAsync()
{
Debug.Log("Before ResetHard command");
await ModelTrackerCommands.ResetHard(); // This command will take some time
Debug.Log("After ResetHard command");
}
public void MyFunction()
{
Debug.Log("Before CatchCommandErrors");
TrackingManager.CatchCommandErrors(MyFunctionAsync());
Debug.Log("After CatchCommandErrors");
}

Calling MyFunction will result in the following Log output:

> Before CatchCommandErrors
> Before ResetHard command
> After CatchCommandErrors
> ...
> After ResetHard command

The execution will return at the await and continues at the calling function. All the code below the await will be executed after the ResetHard command has been finished. So we recommend to put every call that chronologically depends on a command inside an async Task MyFunctionAsync function. The void MyFunction should only contain the TrackingManager.CatchCommandErrors(MyFunctionAsync()) call and should only be used when the execution order is not relevant or when calling a public method via the Unity inspector.