기초취소

취소

이전 예제에는 cancel 함수를 호출하는 흥미로운 부분이 있었습니다.

php
use function Async\spawn;
use function Async\delay;

$progress = spawn(function() use (&$counter, $total) {
    while (true) {
        printProgress($counter, $total);
        delay(1000);
    }
});

processUsers('users.csv', $counter);
$progress->cancel();

이 부분을 제거하면 어떻게 될까요? 직접 시도해 보세요. $progress 코루틴은 1초 지연과 함께 무한 루프를 돕니다. processUsers가 끝나면 제어 흐름은 계속 진행됩니다. $progress 코루틴은 계속 실행됩니다. 영원히요. 결코 멈추지 않습니다. PHP 프로세스도 결코 멈추지 않습니다(외부에서 강제로 종료하지 않는 한).

$progress->cancel()$progress 코루틴을 멈춥니다. 하지만 어떻게 멈출까요?

php
use function Async\spawn;
use function Async\delay;

$progress = spawn(function() use (&$counter, $total) {
    while (true) {
        printProgress($counter, $total);
        
        try {
            delay(1000);
        } catch (Throwable $e) {
            echo get_class($e). PHP_EOL;
            throw $e;
        }
    }
});

processUsers('users.csv', $counter);
$progress->cancel();

delay(1000) 주변의 코드를 바꿔서 무슨 일이 일어나는지 살펴봅시다.

bash
Async\AsyncCancellation

$progress 코루틴이 delay(1000) 안에서 잠들어 있을 때 cancel()이 호출되자, delayAsync\AsyncCancellation 예외를 던졌습니다. 일반 sleep(1)에서도 똑같은 일이 일어납니다. TrueAsync 아래에서는 sleep() 역시 비동기가 되며 마찬가지로 취소 지점입니다 — delay와 똑같이 Async\AsyncCancellation을 던집니다.

코드에서 delay를 사용하는 것은 사실상 다른 코드가 코루틴의 실행을 중단할 수 있게 하는 계약을 맺는 것이라고 말할 수 있습니다. 이는 서로 다른 모듈 간의 관심사를 다시 한번 분리해 주므로 매우 편리합니다.

  1. 코루틴은 언제 자신의 실행이 중단될지 모릅니다.
  2. 코루틴을 취소하는 코드는 코루틴이 정확히 어떻게 중단될지 모릅니다.

코루틴은 어떤 마법으로 멈추는 것이 아니라 예외를 통해 멈춥니다. 코루틴은 임의의 작업 도중에는 취소될 수 없고, 오직 스스로 제어권을 양보하기로 선택한 지점에서만 취소될 수 있습니다. 이런 종류의 취소를 "협력적(cooperative)" 취소라고 부릅니다.