Skip to main content

Asynchronous Activity Completion

How to asynchronously complete an Activity

Asynchronous Activity Completion enables the Activity Function to return without the Activity Execution completing.

There are three steps to follow:

  1. The Activity provides the external system with identifying information needed to complete the Activity Execution. Identifying information can be a Task Token, or a combination of Namespace, Workflow Id, and Activity Id.
  2. The Activity Function completes in a way that identifies it as waiting to be completed by an external system.
  3. The Temporal Client is used to Heartbeat and complete the Activity.

Sometimes Workflows need to perform certain operations in parallel.

Invoking activity stub without the use of yield will return the Activity result promise which can be resolved at later moment. Calling yield on promise blocks until a result is available.

Activity promise also exposes then method to construct promise chains. Read more about Promises here.

Alternatively you can explicitly wrap your code (including yield constucts) using Workflow::async which will execute nested code in parallel with main Workflow code. Call yeild on Promise returned by Workflow::async to merge execution result back to primary Workflow method.

public function greet(string $name): \Generator
{
// Workflow::async runs it's activities and child workflows in a separate coroutine. Use keyword yield to merge
// it back to parent process.

$first = Workflow::async(
function () use ($name) {
$hello = yield $this->greetingActivity->composeGreeting('Hello', $name);
$bye = yield $this->greetingActivity->composeGreeting('Bye', $name);

return $hello . '; ' . $bye;
}
);

$second = Workflow::async(
function () use ($name) {
$hello = yield $this->greetingActivity->composeGreeting('Hola', $name);
$bye = yield $this->greetingActivity->composeGreeting('Chao', $name);

return $hello . '; ' . $bye;
}
);

// blocks until $first and $second complete
return (yield $first) . "\n" . (yield $second);
}

Async completion

There are certain scenarios when moving on from an Activity upon completion of its function is not possible or desirable. For example, you might have an application that requires user input to complete the Activity. You could implement the Activity with a polling mechanism, but a simpler and less resource-intensive implementation is to asynchronously complete a Temporal Activity.

There are two parts to implementing an asynchronously completed Activity:

  1. The Activity provides the information necessary for completion from an external system and notifies the Temporal service that it is waiting for that outside callback.
  2. The external service calls the Temporal service to complete the Activity.

The following example demonstrates the first part:

app/src/AsyncActivityCompletion/GreetingActivity.php

class GreetingActivity implements GreetingActivityInterface
{
private LoggerInterface $logger;

public function __construct()
{
$this->logger = new Logger();
}
/**
* Demonstrates how to implement an Activity asynchronously.
* When {@link Activity::doNotCompleteOnReturn()} is called,
* the Activity implementation function that returns doesn't complete the Activity.
*/
public function composeGreeting(string $greeting, string $name): string
{
// In real life this request can be executed anywhere. By a separate service for example.
$this->logger->info(sprintf('GreetingActivity token: %s', base64_encode(Activity::getInfo()->taskToken)));
// Send the taskToken to the external service that will complete the Activity.
// Return from the Activity a function indicating that Temporal should wait
// for an async completion message.
Activity::doNotCompleteOnReturn();
// When doNotCompleteOnReturn() is invoked the return value is ignored.
return 'ignored';
}
}

The following code demonstrates how to complete the Activity successfully using WorkflowClient:

app/src/AsyncActivityCompletion/CompleteCommand.php

$client = $this->workflowClient->newActivityCompletionClient();
// Complete the Activity.
$client->completeByToken(
base64_decode($input->getArgument('token')),
$input->getArgument('message')
);

To fail the Activity, you would do the following:

// Fail the Activity.
$activityClient->completeExceptionallyByToken($taskToken, new \Error("activity failed"));