Skip to main content
Chevere Workflow provides two execution modes for jobs: synchronous (sync) and asynchronous (async). Understanding the difference between these modes is crucial for optimizing your workflow’s performance.

Execution modes

Asynchronous jobs

By default, all jobs are asynchronous. Async jobs can run in parallel with other async jobs at the same level in the workflow graph.
use function Chevere\Workflow\async;
use function Chevere\Workflow\workflow;

$workflow = workflow(
    job1: async(new FetchUrl(), url: 'https://example.com'),
    job2: async(new FetchUrl(), url: 'https://github.com'),
    job3: async(new FetchUrl(), url: 'https://chevere.org'),
);
In this example, all three jobs will execute simultaneously, significantly reducing total execution time.

Synchronous jobs

Sync jobs execute one at a time, in the order they’re defined. Use sync when you need strict sequential execution or when a job modifies shared state.
use function Chevere\Workflow\sync;
use function Chevere\Workflow\workflow;

$workflow = workflow(
    job1: sync(new FetchUrl(), url: 'https://example.com'),
    job2: sync(new FetchUrl(), url: 'https://github.com'),
    job3: sync(new FetchUrl(), url: 'https://chevere.org'),
);
With sync, each job waits for the previous one to complete before starting.

Performance comparison

Here’s a real example from the source repository demonstrating the performance difference:
demo/sync-vs-async.php
use Chevere\Demo\Actions\FetchUrl;
use function Chevere\Workflow\async;
use function Chevere\Workflow\run;
use function Chevere\Workflow\sync;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

$sync = workflow(
    job1: sync(new FetchUrl(), url: variable('php')),
    job2: sync(new FetchUrl(), url: variable('github')),
    job3: sync(new FetchUrl(), url: variable('chevere')),
);

$async = workflow(
    job1: async(new FetchUrl(), url: variable('php')),
    job2: async(new FetchUrl(), url: variable('github')),
    job3: async(new FetchUrl(), url: variable('chevere')),
);

$variables = [
    'php' => 'https://www.php.net',
    'github' => 'https://github.com/chevere/workflow',
    'chevere' => 'https://chevere.org',
];

// Synchronous execution
$time = microtime(true);
$run = run($sync, ...$variables);
$syncTime = round((microtime(true) - $time) * 1000);

// Asynchronous execution
$time = microtime(true);
$run = run($async, ...$variables);
$asyncTime = round((microtime(true) - $time) * 1000);

echo "Sync: {$syncTime}ms vs Async: {$asyncTime}ms\n";
Async jobs typically complete in a fraction of the time required for sync jobs when operations can run in parallel.

When to use each mode

  • Jobs are independent and don’t share state
  • You want maximum performance
  • Jobs perform I/O operations (API calls, file operations, database queries)
  • Order of execution doesn’t matter
  • Jobs must execute in a specific order
  • A job modifies shared state that subsequent jobs depend on
  • You need to ensure sequential processing
  • Debugging and want predictable execution order

Mixed mode workflows

You can mix sync and async jobs in the same workflow:
$workflow = workflow(
    // These run in parallel
    thumb: async(new ImageResize(), file: variable('image'), fit: 'thumbnail'),
    poster: async(new ImageResize(), file: variable('image'), fit: 'poster'),
    
    // This waits for both above to complete
    store: sync(
        new StoreFiles(),
        files: [response('thumb'), response('poster')]
    ),
);

Converting between modes

You can change a job’s execution mode using withIsSync():
src/Job.php
public function withIsSync(bool $flag = true): JobInterface
{
    $new = clone $this;
    $new->isSync = $flag;

    return $new;
}
Example:
$asyncJob = async(new MyAction(), foo: 'bar');
$syncJob = $asyncJob->withIsSync(true);
Be careful when converting jobs between modes. Changing from async to sync can significantly impact performance, while changing from sync to async may cause race conditions if jobs share state.

Implementation details

The sync() and async() functions are convenience wrappers around the Job class:
src/functions.php
function sync(ActionInterface|string|callable $_, mixed ...$argument): JobInterface
{
    return (new Job($_, ...$argument))->withIsSync(true);
}

function async(ActionInterface|string|callable $_, mixed ...$argument): JobInterface
{
    return new Job($_, ...$argument);
}
During execution, the Runner processes jobs according to the workflow graph, executing async jobs in parallel using PHP’s Amp library.

Best practices

1

Default to async

Start with async jobs for better performance. Only use sync when you have a specific reason.
2

Use dependencies instead of sync

Instead of using sync to control order, use job dependencies with withDepends().
3

Profile your workflows

Measure execution time to understand the impact of sync vs async in your specific use case.
4

Consider I/O vs CPU operations

Async provides the most benefit for I/O-bound operations. CPU-intensive tasks may not see as much improvement.

Build docs developers (and LLMs) love