DesenvolvedorWordPress WhatsApp

Action Scheduler: tarefas em fila e processamento em segundo plano

Quando o WP-Cron não dá conta — milhares de itens, jobs assíncronos, lotes que não podem cair no timeout — o caminho profissional é uma fila de tarefas. O Action Scheduler é essa fila, e provavelmente já está instalado no seu site.

Precisa processar grandes volumes com segurança?

Atualizado em 19/06/2026

Cedo ou tarde, todo projeto WordPress de porte topa com a mesma parede: você precisa processar muita coisa — reindexar 50 mil produtos, enviar 10 mil e-mails, sincronizar um catálogo inteiro com um ERP — e tentar fazer isso dentro de uma única requisição HTTP é receita para timeout, memória esgotada e trabalho pela metade. A resposta madura para esse problema é uma fila de tarefas: você enfileira o trabalho, e ele é processado aos poucos, em segundo plano, com garantia de execução. No ecossistema WordPress, essa fila tem nome: Action Scheduler.

O que é o Action Scheduler

O Action Scheduler é uma biblioteca de fila de tarefas (não um plugin que você instala isoladamente) criada e mantida pela equipe do WooCommerce. Ela é embarcada dentro de plugins — o WooCommerce, o WooCommerce Subscriptions, o Jetpack e centenas de outros já a incluem — então, num site de e-commerce típico, ela já está rodando mesmo que você nunca tenha ouvido falar dela. Quando múltiplos plugins trazem cópias diferentes, a própria biblioteca resolve o conflito carregando a versão mais recente disponível para todo o site.

O que ela faz, em uma frase: grava cada tarefa (uma "ação") em tabelas próprias no banco de dados e as processa em segundo plano, em lotes, com concorrência controlada, retentativa automática em caso de falha e um log de cada execução. As tabelas principais são actionscheduler_actions (a fila em si), actionscheduler_logs (o histórico) e actionscheduler_groups (os grupos de organização).

O "hook" de uma ação é um action comum do WordPress. Você não passa uma função diretamente para a fila: passa o nome de um hook. Quem faz o trabalho de verdade é a função que você liga a esse hook com add_action() — exatamente como em qualquer outro hook do WordPress. Esse callback precisa estar registrado em toda requisição, não só quando você enfileira.

Por que não usar só o WP-Cron?

O WP-Cron é ótimo para o que foi feito: alguns eventos esporádicos e recorrentes. Mas ele guarda toda a fila dentro de uma única option (cron) serializada, sem concorrência, sem retentativa real e sem visibilidade. Coloque milhares de eventos ali e essa option vira um gargalo que é lido e reescrito a cada requisição. O Action Scheduler nasceu justamente para resolver isso em escala. A tabela abaixo resume quando usar cada um.

CritérioWP-CronAction Scheduler
Armazenamento1 option serializadaTabelas dedicadas, indexadas
Escala (nº de jobs)DezenasMilhares / dezenas de milhares
Retentativa em falhaNão (o evento some)Sim, com reagendamento
ConcorrênciaSem controleLotes com limite configurável
Execução assíncrona ("o quanto antes")Não nativamenteSim (as_enqueue_async_action)
Log / auditoriaNãoSim, por ação
UI de monitoramentoPlugin externoSim (via WooCommerce)
Quando usar1–2 eventos recorrentes simplesLotes grandes, async, garantia de execução

A API principal

A interface pública do Action Scheduler é um punhado de funções as_*. As que você vai usar no dia a dia:

Sempre verifique function_exists() antes de chamar. O Action Scheduler é uma biblioteca de terceiros; se o site não tiver WooCommerce nem nenhum plugin que a embarque, essas funções simplesmente não existem e o seu código quebra com um fatal error. A guarda if ( function_exists( 'as_enqueue_async_action' ) ) é obrigatória em código que será distribuído.

1. Enfileirar uma ação assíncrona

O caso mais comum: algo aconteceu (um pedido foi pago, um formulário foi enviado) e você quer fazer um trabalho potencialmente lento sem segurar a resposta para o usuário. Enfileire a ação e devolva a página na hora; o trabalho roda em background.

<?php
// Em algum ponto do fluxo (ex.: ao concluir um pedido), enfileiramos o trabalho.
// Note o GROUP: agrupar facilita o monitoramento e a limpeza.
function dwp_apos_pedido_pago( $order_id ) {
    if ( ! function_exists( 'as_enqueue_async_action' ) ) {
        return; // Action Scheduler ausente: faça um fallback ou apenas saia.
    }

    // Passamos só o ID — nunca o objeto inteiro do pedido.
    // Array POSICIONAL: cada item vira um parâmetro separado no handler.
    as_enqueue_async_action(
        'dwp_sincroniza_pedido_erp', // hook (action) a disparar
        array( (int) $order_id ),    // args leves (posicionais)
        'dwp-sync'                   // group
    );
}
add_action( 'woocommerce_order_status_processing', 'dwp_apos_pedido_pago' );

// O HANDLER: ligado ao hook com add_action, registrado SEMPRE.
// Aridade 1: recebe 1 argumento (o único item do array de args).
add_action( 'dwp_sincroniza_pedido_erp', 'dwp_handler_sincroniza_pedido', 10, 1 );

function dwp_handler_sincroniza_pedido( $order_id ) {
    // O Action Scheduler espalha os itens do array como parâmetros posicionais.
    $order_id = (int) $order_id;
    if ( ! $order_id ) {
        return;
    }

    $order = wc_get_order( $order_id ); // recarregamos o objeto pelo ID
    if ( ! $order ) {
        return;
    }

    // Idempotência: se já sincronizamos, não repita (evita duplicar na retentativa).
    if ( $order->get_meta( '_dwp_sincronizado' ) === 'sim' ) {
        return;
    }

    // ... aqui o trabalho lento de verdade (chamada HTTP ao ERP etc.) ...

    $order->update_meta_data( '_dwp_sincronizado', 'sim' );
    $order->save();
}

Repare em dois detalhes que separam o código amador do profissional: o argumento é apenas o order_id (referência leve, recarregada dentro do handler) e a verificação de idempotência logo no início. Como veremos, o Action Scheduler pode reexecutar a sua ação — então ela precisa tolerar isso.

Como o handler recebe os argumentos (armadilha comum). Ao executar a ação, o Action Scheduler chama internamente do_action_ref_array( $hook, array_values( $args ) ) — ou seja, ele descarta as chaves do array de args e espalha os valores como parâmetros posicionais para o callback. Por isso, enfileirar com array associativo (array( 'order_id' => 5 )) e ler no handler via $args['order_id'] não funciona: o callback recebe 5 como primeiro parâmetro, e $args['order_id'] vira null. Use sempre arrays posicionais, declare a aridade certa no add_action( 'hook', 'handler', 10, $n ) (igual ao número de itens) e receba cada item como um argumento separado — nunca dependa de chaves associativas.

2. Agendar uma ação recorrente (com guarda)

Para tarefas periódicas — uma varredura de manutenção, um resumo diário —, use as_schedule_recurring_action(). O padrão correto é registrar a recorrência apenas se ela ainda não existir, para não criar duplicatas a cada carregamento de plugin:

<?php
add_action( 'init', 'dwp_agenda_recorrentes' );

function dwp_agenda_recorrentes() {
    // Guarda dupla: a biblioteca pode não existir...
    if ( ! function_exists( 'as_has_scheduled_action' ) ) {
        return;
    }

    // ...e a ação pode já estar agendada de execuções anteriores.
    if ( as_has_scheduled_action( 'dwp_limpeza_diaria', array(), 'dwp-manutencao' ) ) {
        return;
    }

    as_schedule_recurring_action(
        time(),                 // primeiro disparo: agora
        DAY_IN_SECONDS,         // intervalo em SEGUNDOS (não use strings tipo 'daily')
        'dwp_limpeza_diaria',   // hook
        array(),                // sem args
        'dwp-manutencao'        // group
    );
}

// Handler da recorrente
add_action( 'dwp_limpeza_diaria', 'dwp_executa_limpeza' );
function dwp_executa_limpeza() {
    // ... limpeza de logs antigos, caches órfãos etc. ...
}

Diferentemente do WP-Cron, o intervalo aqui é um número de segundos, não uma string de recorrência ('hourly', 'daily'). Passar uma string não vai funcionar como você espera. Use as constantes do núcleo — HOUR_IN_SECONDS, DAY_IN_SECONDS, WEEK_IN_SECONDS — para manter o código legível.

3. O padrão de lote: processar N e re-enfileirar o próximo

Este é o padrão mais importante do artigo e o motivo número um para se usar o Action Scheduler. Você nunca tenta processar 50 mil itens em uma única ação — isso estouraria a memória e o tempo de execução. Em vez disso, cada ação processa um lote pequeno (por exemplo, 50 itens) e, se ainda houver trabalho, enfileira a próxima ação para continuar de onde parou. A fila se auto-alimenta até terminar.

<?php
// Disparo inicial (uma vez, ex.: ao clicar num botão no admin):
// as_enqueue_async_action( 'dwp_reindexa_lote', array( 0 ), 'dwp-reindex' );

// Aridade 1: o offset chega como parâmetro posicional (não como array de chaves).
add_action( 'dwp_reindexa_lote', 'dwp_processa_lote', 10, 1 );

function dwp_processa_lote( $offset ) {
    $offset    = (int) $offset; // 1º (e único) item do array de args
    $por_lote  = 50; // tamanho do lote: ajuste conforme o peso de cada item

    $ids = get_posts( array(
        'post_type'      => 'product',
        'post_status'    => 'publish',
        'fields'         => 'ids',
        'posts_per_page' => $por_lote,
        'offset'         => $offset,
        'orderby'        => 'ID',
        'order'          => 'ASC',
        'no_found_rows'  => true, // não precisamos do total: mais leve
    ) );

    // Sem mais itens: o trabalho acabou. NÃO re-enfileira.
    if ( empty( $ids ) ) {
        return;
    }

    foreach ( $ids as $id ) {
        // ... reindexa o produto $id ...
        clean_post_cache( $id ); // higiene de memória em loops longos
    }

    // Ainda pode haver mais: enfileira o PRÓXIMO lote, avançando o offset.
    if ( function_exists( 'as_enqueue_async_action' ) ) {
        as_enqueue_async_action(
            'dwp_reindexa_lote',
            array( $offset + $por_lote ), // próximo offset (posicional)
            'dwp-reindex'
        );
    }
}

Esse desenho tem propriedades excelentes: cada execução é curta (não dá timeout), o consumo de memória é constante, e se uma ação falhar o Action Scheduler a repete — sem perder o lugar na fila, porque o offset está gravado nos argumentos. É exatamente assim que ferramentas sérias importam e reindexam grandes catálogos no WooCommerce.

Cuidado ao usar offset com dados que mudam. Se itens são apagados durante o processamento, o offset pode "pular" registros. Em jobs onde isso importa, prefira paginar por uma âncora estável — por exemplo, WHERE ID > $ultimo_id ORDER BY ID — guardando o último ID processado em vez de um deslocamento numérico.

4. Cancelar e consultar ações

Para desfazer um agendamento (recorrente ou único), use as_unschedule_action() com a mesma assinatura usada no agendamento — mesmo hook, mesmos args, mesmo group. Ele cancela a ação pendente correspondente; numa ação recorrente, isso encerra o agendamento por completo (não apenas pula a próxima ocorrência). Se houver várias pendentes do mesmo hook, as_unschedule_all_actions() remove todas. Para apenas saber se há algo agendado e quando, use as_next_scheduled_action():

<?php
// Ao desativar o plugin, é boa prática limpar as recorrências.
register_deactivation_hook( __FILE__, 'dwp_ao_desativar' );

function dwp_ao_desativar() {
    if ( function_exists( 'as_unschedule_action' ) ) {
        // Cancela a ação pendente (assinatura idêntica ao agendamento).
        // Numa recorrente, isto ENCERRA o agendamento — não pula só uma vez.
        as_unschedule_action( 'dwp_limpeza_diaria', array(), 'dwp-manutencao' );

        // Para remover VÁRIAS ações pendentes do mesmo hook de uma vez:
        // as_unschedule_all_actions( 'dwp_limpeza_diaria', array(), 'dwp-manutencao' );
    }
}

// Consultar a próxima execução (ex.: para exibir no admin)
if ( function_exists( 'as_next_scheduled_action' ) ) {
    $ts = as_next_scheduled_action( 'dwp_limpeza_diaria', array(), 'dwp-manutencao' );
    if ( $ts ) {
        // $ts é um timestamp Unix em UTC; converta para o fuso do site ao exibir.
        $quando = wp_date( 'd/m/Y H:i', $ts );
        // ... mostre "Próxima limpeza: $quando" ...
    }
}

Monitoramento e disparo em produção

Com o WooCommerce ativo, a tela WooCommerce › Status › Scheduled Actions lista cada ação por status — pending, in-progress, complete, failed, canceled — com o log completo de cada uma. Filtre pelo group do seu plugin (no exemplo, dwp-sync ou dwp-reindex) para isolar as suas ações no meio das dezenas que o WooCommerce gera. Pela linha de comando, os comandos wp action-scheduler list e wp action-scheduler run permitem inspecionar e forçar o processamento da fila:

# Listar ações de um grupo, mais recentes primeiro:
wp action-scheduler list --group=dwp-reindex --per-page=20

# Forçar o processamento da fila AGORA (ideal no cron de sistema):
wp action-scheduler run

# Quantas ações há em cada status?
wp action-scheduler status

E no wp-config.php, para tirar o processamento das mãos do tráfego e colocá-lo num cron real:

<?php
// wp-config.php — desliga o disparo por loopback a cada visita.
define( 'DISABLE_WP_CRON', true );
// No crontab do servidor, dispare a cada minuto, por exemplo:
//   * * * * * cd /var/www/seusite && wp action-scheduler run --quiet

A fila ainda depende de um gatilho. O Action Scheduler resolve a escala e a confiabilidade, mas se apoia no WP-Cron para "acordar" e processar — logo, herda a dependência de tráfego. Num site de baixo movimento, isso significa atrasos. Em produção, o padrão é definir DISABLE_WP_CRON como true no wp-config.php e disparar wp-cron.php (ou wp action-scheduler run) por um cron de sistema a cada minuto. Veja o detalhamento no artigo de WP-Cron.

Armadilhas comuns

Boas práticas

Se o seu projeto envolve WooCommerce, a boa notícia é que você não precisa instalar nada: a fila já está lá, esperando para ser usada com responsabilidade. E se você está empacotando isso num plugin próprio, vale revisar como organizar o registro dos hooks no artigo sobre criar um plugin.

Tem um processo pesado travando o seu site?

Implemento filas de tarefas com Action Scheduler — importações, sincronização com ERP, e-mails em massa — sem timeout e com retentativa. Me chame no WhatsApp.

WhatsApp: (43) 99932-9697

ou e-mail: [email protected]

Perguntas frequentes

Preciso instalar o Action Scheduler separadamente?

Quase nunca. O Action Scheduler é uma biblioteca embarcada em muitos plugins populares — o WooCommerce, o WooCommerce Subscriptions, o Jetpack e dezenas de outros já o trazem. Quando vários plugins incluem cópias diferentes, a própria biblioteca decide qual versão (a mais recente) é carregada para todo o site. Se você desenvolve um plugin que não depende do WooCommerce, pode incluir a biblioteca via Composer (woocommerce/action-scheduler) ou baixá-la do site oficial. Sempre proteja as chamadas com function_exists( 'as_enqueue_async_action' ), porque a função pode não existir.

Qual a diferença entre Action Scheduler e WP-Cron?

O WP-Cron é um agendador simples: guarda eventos em uma única option e dispara o hook na próxima visita após o horário. O Action Scheduler é uma fila de tarefas: grava cada ação em tabelas próprias do banco, processa em lotes com concorrência controlada, repete ações que falham e mantém um log de cada execução. Para alguns eventos esporádicos, WP-Cron basta; para milhares de jobs, processamento assíncrono ou lotes grandes, o Action Scheduler é a escolha certa.

O Action Scheduler executa tarefas sem ninguém visitar o site?

Por padrão, não. Ele se ancora no WP-Cron para acordar e processar a fila, então herda a mesma limitação: depende de tráfego ou de um cron real do sistema. Em produção séria, desligue o DISABLE_WP_CRON e dispare wp-cron.php (ou wp action-scheduler run via WP-CLI) por um cron de sistema a cada minuto. Sem isso, em sites de baixo movimento, as ações podem atrasar horas.

O que significa uma ação ser idempotente e por que isso importa?

Idempotente significa que rodar a ação duas vezes produz o mesmo resultado de rodá-la uma vez. Como o Action Scheduler pode repetir uma ação que falhou no meio (ou que o processo perdeu por timeout), o seu handler precisa tolerar reexecução: verifique se o trabalho já foi feito antes de fazê-lo de novo (por exemplo, cheque um meta de "já processado" antes de cobrar um cliente). Sem idempotência, uma retentativa pode duplicar e-mails, pedidos ou cobranças.

Posso passar objetos grandes nos argumentos da ação?

Não passe. Os argumentos são serializados e gravados na tabela actionscheduler_actions, e o Action Scheduler usa uma hash deles para evitar duplicatas. Passar um array enorme (um pedido inteiro, um post completo) incha o banco, pode estourar limites de coluna e torna a deduplicação imprevisível. Passe sempre uma referência leve — um ID — e recarregue o objeto dentro do handler com get_post(), wc_get_order() etc.

Como monitoro as ações em produção?

Com o WooCommerce ativo, vá em WooCommerce › Status › Scheduled Actions (ou Ferramentas › Ações agendadas, dependendo da versão). Lá você vê ações pendentes, em execução, concluídas e com falha, com o log de cada uma. Pela linha de comando, wp action-scheduler list e wp action-scheduler run ajudam a inspecionar e forçar o processamento. Filtre pelo group do seu plugin para não se perder no meio das ações do WooCommerce.

Referências oficiais

  1. Action Scheduler — Site oficial
  2. Action Scheduler — Usage
  3. Action Scheduler — API Reference
  4. WooCommerce — Developer Documentation
Tem alguma dúvida? Ficarei feliz em ajudar. Clique Aqui