Programmers who are used to pre-emptive threads (or just plain singletasking) instinctively use loops for repetitive tasks. Long-running loops are bad in cooperative multitasking environments, however. Nothing else may run in the meantime.

Consider:

sub long_task {
  print "Working... .";
  my $counter = 0;
  while ($counter < 1_000_000) {
    print "\b", substr("|/-\\", $counter % 4);
    $counter++;
  }
  print "\bdone!\n";
}

That's going to take an awful long time. If it's done in a POE program, nothing else will happen until it finishes.

That's not bad in singletasking POE programs, but most people use POE for its multitasking ability. Breaking the loop into event handlers will make it cooperate with other sessions:

sub long_task_start {
  my ($kernel, $heap) = @_[KERNEL, HEAP];
  print "Working... .";
  $heap->{counter} = 0;
  $kernel->yield("long_task_continue");
}

sub long_task_continue {
  my ($kernel, $heap) = @_[KERNEL, HEAP];
  if ($heap->{counter} < 1_000_000) {
    print "\b", substr("|/-\\", $heap->{counter} % 4);
    $heap->{counter}++;
    $kernel->yield("long_task_continue");
  }
  else {
    print "\bdone!\n";
  }
}

Each event is tied to some code (in this case a subroutine with the same name), so receiving "long_task_continue" is the same as calling &long_task_continue. Unlike with while, though, POE::Kernel is free to dispatch other events between each iteration of the long task's loop.

Breaking the loop up like this will make it a lot slower. Often each iteration will be relatively fast, but the sheer number of iterations makes the whole loop slow. In that case, each "long_task_continue" event could trigger several iterations. Here each "long_task_continue" event triggers a hundred iterations. The entire task should take a more reasonable time to run.

sub long_task_continue {
  my ($kernel, $heap) = @_[KERNEL, HEAP];
  my $timeslice_counter = 0;

  while ($heap->{counter} < 1_000_000
    and $timeslice_counter++ < 100) {
    print "\b", substr("|/-\\", $heap->{counter} % 4);
    $heap->{counter}++;
  }

  if ($heap->{counter} < 1_000_000) {
    $kernel->yield("long_task_continue");
  }
  else {
    print "\bdone!\n";
  }
}

The number of iterations can be adjusted to balance speed with cooperation.


See also: /Waiting