Synchronous or Asynchronous?

Pepper runs 2 different processes:

  • one on his tablet,
  • one on his head.

There is, between these 2 CPUs, a flow of information done with TCP/IP via USB:

../_images/head_tablet_communication.png

You can design your code in a flexible way and decide if you want to handle this communication synchronously or asynchronously.

However, best practices exist and the choice between working synchronously or asynchronously depends on some factors that we’ll describe below.

On the UI thread

The Android system gives you access to the UI thread, using the different Activity lifecycle callbacks for example:

override fun onCreate(savedInstanceState: Bundle?) {
    // Executes on the UI thread.
}

override fun onResume() {
    // Executes on the UI thread.
}

...
@Override
protected void onCreate(Bundle savedInstanceState) {
    // Executes on the UI thread.
}

@Override
protected void onResume() {
    // Executes on the UI thread.
}

...

If you perform a synchronous call while on the UI thread, this would block the UI and result in a poor user experience.

To prevent this, when you work on the UI thread, you must use the QiSDK asynchronous calls, otherwise a NetworkOnMainThreadException will be thrown.

In practice

Don’t:

// UI thread.
val say: Say = SayBuilder.with(qiContext)
        .withText("Hello")
        .build() // Throws a NetworkOnMainThreadException.
// UI thread.
Say say = SayBuilder.with(qiContext)
        .withText("Hello")
        .build(); // Throws a NetworkOnMainThreadException.
// UI thread.
goTo.run() // Throws a NetworkOnMainThreadException.
// UI thread.
goTo.run(); // Throws a NetworkOnMainThreadException.

Do:

// UI thread.
val sayBuilding: Future<Say> = SayBuilder.with(qiContext)
        .withText("Hello")
        .buildAsync() // OK.
// UI thread.
Future<Say> sayBuilding = SayBuilder.with(qiContext)
        .withText("Hello")
        .buildAsync(); // OK.
// UI thread.
goTo.async().run() // OK.
// UI thread.
goTo.async().run(); // OK.

On a worker thread

You have multiple ways to work on a worker thread on Android, either using your own thread management system or a library handling it for you.

The QiSDK provides multiple ways to put work on a worker thread:

Robot Lifecycle:

override void onRobotFocusGained(qiContext: QiContext) {
    // Executes on a worker thread.
}

override fun onRobotFocusLost() {
    // Executes on a worker thread.
}
@Override
public void onRobotFocusGained(QiContext qiContext) {
    // Executes on a worker thread.
}

@Override
public void onRobotFocusLost() {
    // Executes on a worker thread.
}

Futures chaining:

future.thenConsume {
    // Executes on a worker thread.
}
future.thenConsume(future -> {
    // Executes on a worker thread.
});

Listeners:

val humanAwareness: HumanAwareness = qiContext.humanAwareness
humanAwareness.addOnHumansAroundChangedListener { humans ->
    // Executes on a worker thread.
}
HumanAwareness humanAwareness = qiContext.getHumanAwareness();
humanAwareness.addOnHumansAroundChangedListener(humans -> {
    // Executes on a worker thread.
});

In practice

If you want to handle cancellation:

Use asynchronous calls.

val goToFuture: Future<Void> = goTo.async().run()

..

goToFuture.requestCancellation()
Future<Void> goToFuture = goTo.async().run();

...

goToFuture.requestCancellation();

If you want to run multiple actions simultaneously:

Use asynchronous calls.

val sayFuture: Future<Void>  = say.async().run()
val goToFuture: Future<Void>  = goTo.async().run()
Future<Void> sayFuture = say.async().run();
Future<Void> goToFuture = goTo.async().run();

In other cases:

Use synchronous or asynchronous calls.

say.run()
say.run();

Or

val sayFuture: Future<Void> = say.async().run()
Future<Void> sayFuture = say.async().run();

Summary

The following chart illustrates the call types to use depending on the context:

../_images/sync_async.png

See also