最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Use cron to create a non blocking task

programmeradmin0浏览0评论

I'm writing a plugin which collects a pretty large feed. From each feed item a custom post will be created. The feed can also contain images for each item that is uploaded from the remote url. Processing the entire feed can take some time and since I would like to do the processing in the background I am looking into cron as a way to achieve this. Currently I'm doing something like this:

class Cron_Task {
  const DEFAULT_TASK_INTERVAL = 30;

  public function __construct($taskName, $data) {
    add_filter('cron_schedules', array($this, 'addSchedule'));
    add_action('cron_task', 'Cron_Task::run');
    if (!wp_next_scheduled('cron_task', array($taskName))) {
      // Store data in cache.
      set_transient($taskName, serialize($data));
      wp_schedule_event(time(), $taskName, 'cron_task', array($taskName));
    }
  }

  public static function processItem($data) {
    // Execute long running task.
  }

  public function addSchedule($schedules) {
    $schedules['crontask'] = array(
      'interval' => self::DEFAULT_TASK_INTERVAL,
      'display' => __('Cron_Task'),
    );
    return $schedules;
  }

  public static function run($taskName) {
    // Get data from cache.
    $data = unserialize(get_transient($taskName));
    if ($data) {
      $item = array_shift($data);
      // Process one item.
      self::processItem($item);

      // Check if we have processed all items.
      if (!count($data)) {
        delete_transient($taskName);
        wp_clear_scheduled_hook('cron_task', array($taskName));
      } else {
        // Update the items to process.
        set_transient($taskName, serialize($data));
      }
    }

  }
}

// When a feed is collected we create a cron_task to be run every 30 seconds.
if (get_feed()) {
  $cronTask = new Cron_Task('testEvent', $feed);
}

The above sort of works, but I think there must be a better way of doing this kind of thing? For instance, since the configured cron tasks will only be run on page load, the task might not be run at the desired interval if there are no visitors.

I'm writing a plugin which collects a pretty large feed. From each feed item a custom post will be created. The feed can also contain images for each item that is uploaded from the remote url. Processing the entire feed can take some time and since I would like to do the processing in the background I am looking into cron as a way to achieve this. Currently I'm doing something like this:

class Cron_Task {
  const DEFAULT_TASK_INTERVAL = 30;

  public function __construct($taskName, $data) {
    add_filter('cron_schedules', array($this, 'addSchedule'));
    add_action('cron_task', 'Cron_Task::run');
    if (!wp_next_scheduled('cron_task', array($taskName))) {
      // Store data in cache.
      set_transient($taskName, serialize($data));
      wp_schedule_event(time(), $taskName, 'cron_task', array($taskName));
    }
  }

  public static function processItem($data) {
    // Execute long running task.
  }

  public function addSchedule($schedules) {
    $schedules['crontask'] = array(
      'interval' => self::DEFAULT_TASK_INTERVAL,
      'display' => __('Cron_Task'),
    );
    return $schedules;
  }

  public static function run($taskName) {
    // Get data from cache.
    $data = unserialize(get_transient($taskName));
    if ($data) {
      $item = array_shift($data);
      // Process one item.
      self::processItem($item);

      // Check if we have processed all items.
      if (!count($data)) {
        delete_transient($taskName);
        wp_clear_scheduled_hook('cron_task', array($taskName));
      } else {
        // Update the items to process.
        set_transient($taskName, serialize($data));
      }
    }

  }
}

// When a feed is collected we create a cron_task to be run every 30 seconds.
if (get_feed()) {
  $cronTask = new Cron_Task('testEvent', $feed);
}

The above sort of works, but I think there must be a better way of doing this kind of thing? For instance, since the configured cron tasks will only be run on page load, the task might not be run at the desired interval if there are no visitors.

Share Improve this question edited Mar 13, 2018 at 11:55 fuxia 107k38 gold badges255 silver badges459 bronze badges asked Mar 13, 2018 at 9:58 CyclonecodeCyclonecode 1,1841 gold badge9 silver badges32 bronze badges 6
  • if there is no traffic why do you care that the site is not up to date? (serious question, not trying to troll) – Mark Kaplun Commented Mar 13, 2018 at 10:22
  • @MarkKaplun - This is more a question how to perform async tasks at certain intervals in wordpress. I only wrote that there might be no visitors to pin point the problem in this case. – Cyclonecode Commented Mar 13, 2018 at 10:50
  • if you write a plugin, wordpress cron is the only tool you can be sure to be there. @swissspidy answer is good way to avoid the "no traffic" issue, but it should be fun to make site owners to properly configure it, and totally rare for them to even have access to wpcli – Mark Kaplun Commented Mar 13, 2018 at 10:55
  • .... and as I commented of his answer I believe the best way to go is with you supplying wpcli integration in your plugin. and then, those that can, will be able to trigger the update any way they will want. – Mark Kaplun Commented Mar 13, 2018 at 10:57
  • @MarkKaplun - If you simply would like to call wp-cron.php at a regular interval then the simplest way would be to just add a new cron job using crontab -e =) This question is like I said more about a good way to implement async tasks, like this project: github/techcrunch/wp-async-task – Cyclonecode Commented Mar 13, 2018 at 11:09
 |  Show 1 more comment

2 Answers 2

Reset to default 1

For instance, since the configured cron tasks will only be run on page load, the task might not be run at the desired interval if there are no visitors.

To prevent this, you need to use your operating system's task scheduler.

For this, you need to define define('DISABLE_WP_CRON', true); in your wp-config.php file. After that, you'd add a crontab configuration like this:

/10 * * * * curl http://YOUR_SITE_URL/wp-cron.php > /dev/null 2>&1

This configuration would call http://YOUR_SITE_URL/wp-cron.php every ten minutes. Tasks that are scheduled at this point will be executed.

You can also use WP-CLI for that:

*/10 * * * * cd /var/www/example/htdocs; wp cron event run --due-now > /dev/null 2>&1

I had a similar problem to solve and, as it's plugin development, messing with wp-config.php or using WP-CLI are not options. I resolved this as follows.

My scheduled task runs a function that invokes an asynchronous action, via wp_remote_post.

if ( ! wp_next_scheduled( 'my_long_running_event' ) ) {
    wp_schedule_event( $start_time, $recurrence, 'my_long_running_event' );
}

add_action( 'my_long_running_event', 'invoke_my_long_running_function' );
function invoke_my_long_running_function() {
    $nonce = wp_create_nonce( 'my_nonce' . 'my_action' );
    $url = admin_url( 'admin-post.php' );
    $args = array(
        'method'      => 'POST',
        'timeout'     => 5,
        'redirection' => 5,
        'blocking'    => false,
        'headers'     => array(),
        'body'        => array(
            'action'   => 'my_long_running_action',
            'my_nonce' => $nonce,
        ),
    );
    return wp_remote_post( $url, $args );
}

The most important part is 'blocking' => false because this runs the action asynchronously.

The use of a nonce is optional but helps prevent the long-running function being invoked directly. If you ever need to call it in additional to the schedule, call your equivalent of invoke_my_long_running_function() instead.

Then you need an admin_post action hook to actually/finally run your function.

add_action( 'admin_post_nopriv_my_long_running_action', 'my_long_running_function' );

The use of nopriv is not ideal but the scheduled job is not authenticated, so the nopriv is essential, or the action won't run.

Of course, you also need your equivalent of my_long_running_function.

function my_long_running_function() {
    // Do the heavy work here.
}
发布评论

评论列表(0)

  1. 暂无评论