CodeGym /행동 /C# SELF /작업 취소: CancellationToken

작업 취소: CancellationToken

C# SELF
레벨 60 , 레슨 4
사용 가능

1. CancellationToken 소개

상황을 생각해보세요: 긴 파일 다운로드를 시작했는데, 나중에 요금제 때문에 데이터가 아까워진다거나 사용자가 실수로 계산을 시작했다가 취소하고 싶을 수 있습니다. 사용자는 “취소” 버튼을 누르고 결과를 기다리고 싶지 않을 수 있죠. 현대 애플리케이션은 반응성이 좋아야 하고, 언제든 중단 신호를 전달해서 비동기 작업을 올바르게 중지할 수 있어야 합니다.

바로 이걸 위해 .NET에는 취소 인프라가 있습니다 — CancellationToken.

CancellationToken(말 그대로 "취소 토큰")은 긴 작업 안으로 전달하는 특수 객체입니다. 언제든지 CancellationTokenSource를 통해 취소를 신호할 수 있고, 작업은 주기적으로 토큰을 확인(예: IsCancellationRequested)해서 필요하면 ThrowIfCancellationRequested()를 호출하며 깔끔하게 종료해야 합니다.

구조(간단히)

  • CancellationTokenSource라는 객체가 있어서 토큰을 생성하고 취소할 수 있습니다(메서드 Cancel()).
  • CancellationToken 자체는 여러 작업에 나눠줄 수 있는 "신호 깃발"입니다(소스의 Token 프로퍼티를 통해).
  • 작업은 주기적으로 토큰을 확인합니다: 취소가 요청되면 작업을 종료하거나 OperationCanceledException을 던집니다.

비유하자면: 당신은 상사(당신이 CancellationTokenSource)이고, 직원들에게 배지를 나눠줍니다(그게 CancellationToken). 모든 걸 중단하라고 판단되면 빨간 깃발을 올리면, 배지를 가진 사람들은 즉시 철수합니다.

2. CancellationToken 사용법

취소 토큰 소스 생성(CancellationTokenSource)

var cts = new CancellationTokenSource();

토큰 얻기(CancellationToken)

CancellationToken token = cts.Token;

비동기 메서드에 토큰 전달하기

대부분의 표준 비동기 메서드는 CancellationToken 타입 파라미터를 받습니다. 예: HttpClient.GetAsync, Stream.ReadAsync, Task.Delay 등.

예시 — 취소 가능한 지연:

await Task.Delay(10000, token); // 10초 기다리지만 취소할 수 있음!

취소 요청(예: 버튼이나 타이머로)

cts.Cancel(); // 이 토큰을 받은 모든 작업이 취소 신호를 받음

메서드 내부에서 토큰 확인하기

특히 작업이 길고 루프성이라면 주기적으로 취소 플래그를 확인하고 요청되었으면 OperationCanceledException을 던져야 합니다:

token.ThrowIfCancellationRequested();

또는 단순히 프로퍼티를 확인할 수도 있습니다:

if (token.IsCancellationRequested)
{
    // 리소스 정리하고 메서드 종료
}

3. 예제: 학습용 앱에 취소 추가하기

예를 들어 사이트에서 데이터를 다운받는 앱이 있다고 합시다. 사용자가 마음을 바꿨을 때 다운로드를 취소할 수 있게 만들어봅시다.

비동기 다운로드 기본 예제

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

class Downloader
{
    public async Task DownloadAsync(string url)
    {
        var client = new HttpClient();
        string content = await client.GetStringAsync(url); // 취소 없음
        Console.WriteLine("다운로드 완료!");
    }
}

CancellationToken 추가하기

public async Task DownloadAsync(string url, CancellationToken token)
{
    var client = new HttpClient();
    string content = await client.GetStringAsync(url, token); // 이제 취소 지원!
    Console.WriteLine("다운로드 완료!");
}

호출자 쪽에서 취소 제어

static async Task Main(string[] args)
{
    var downloader = new Downloader();
    var cts = new CancellationTokenSource();

    Console.WriteLine("다운로드할 URL을 입력하세요:");
    string url = Console.ReadLine();

    var downloadTask = downloader.DownloadAsync(url, cts.Token);

    Console.WriteLine("다운로드를 취소하려면 아무 키나 누르세요...");
    Console.ReadKey();

    cts.Cancel(); // 취소 신호

    try
    {
        await downloadTask;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("사용자에 의해 다운로드가 취소되었습니다!");
    }
}

이렇게 간단합니다! 이제 사용자는 언제든 작업을 중단할 수 있습니다.

4. CancellationTokenSource와 메서드의 상호작용

flowchart TD
    A["사용자 코드 (Main)"] -- 생성 --> B["CancellationTokenSource"]
    B -- 발급 --> C["CancellationToken"]
    C -- 전달 --> D["비동기 작업"]
    A -- Cancel() 호출 --> B
    D -- 주기적으로 확인 --> C
    C -- 취소 알림 --> D
    D -- Exception 던지거나 종료 --> A

5. 취소 처리

작업이 취소 토큰을 받으면 두 가지 동작이 있습니다:

.NET 메서드가 스스로 예외를 던지는 경우.
Stream.ReadAsync, HttpClient.GetAsync, Task.Delay 같은 표준 메서드에 토큰을 전달하면, Cancel()가 호출될 때 이 메서드들이 스스로 OperationCanceledException을 던집니다. 호출자는 이 예외만 잡으면 됩니다.

사용자 구현 비동기 코드.
직접 긴 작업(루프 처리나 무거운 계산 등)을 구현한다면, token.IsCancellationRequested를 정기적으로 확인하거나 token.ThrowIfCancellationRequested()를 호출해 취소에 적절히 반응하는 건 당신 책임입니다.

예시: 수동으로 취소를 확인하는 긴 작업

public async Task CalculatePrimesAsync(int max, CancellationToken token)
{
    for (int i = 2; i < max; i++)
    {
        token.ThrowIfCancellationRequested(); // 취소 확인

        if (IsPrime(i))
        {
            Console.WriteLine($"소수: {i}");
            await Task.Delay(100, token); // 잠깐 쉬기(취소 가능)
        }
    }
    Console.WriteLine("계산 완료!");
}

private bool IsPrime(int n)
{
    for (int i = 2; i <= Math.Sqrt(n); i++)
        if (n % i == 0) return false;
    return true;
}

6. 유용한 팁

CancellationToken을 지원하는 메서드와 클래스

클래스/메서드 CancellationToken 지원? 사용 예시
Task.Delay
await Task.Delay(1000, token);
HttpClient.GetAsync
await client.GetAsync(url, token);
Stream.ReadAsync
await stream.ReadAsync(buffer, 0, len, token);
Task.Run
Task.Run(() => ..., token);
File.ReadAllTextAsync
await File.ReadAllTextAsync(path, token);
Thread.Sleep
지원하지 않음; 대신 Task.Delay 사용 권장
사용자 메서드 ✔ (지원 추가 시)
MyLongMethod(token)

취소 가능한 작업의 생명주기

sequenceDiagram
    participant 사용자
    participant Main
    participant CancellationTokenSource
    participant 비동기작업

    사용자->>Main: 작업 시작
    Main->>CancellationTokenSource: CTS 생성
    Main->>비동기작업: 실행 및 CancellationToken 전달
    사용자->>Main: "취소" 누름
    Main->>CancellationTokenSource: Cancel() 호출
    비동기작업->>비동기작업: 취소 감지 (\nIsCancellationRequested)
    비동기작업-->>Main: OperationCanceledException 던짐
    Main->>사용자: "작업이 취소되었습니다" 메시지 표시

실무에서의 사용 사례

  • UI 애플리케이션: 긴 다운로드, 계산, 파일 작업을 사용자가 창을 닫거나 작업을 취소하면 중단.
  • 서버 애플리케이션: 클라이언트가 연결을 끊으면 요청 처리를 바로 취소해서 리소스 낭비를 막음.
  • 대용량 데이터 처리: 작업이 너무 길면 중단할 수 있게 해서 계산이나 마이그레이션을 멈출 수 있게 함.
  • 하드웨어 통합: 스캔, 프린트 같은 작업을 긴급히 중단해야 할 때 취소 지원은 필수.

7. CancellationToken 사용 시 흔한 실수

실수 #1: 토큰 확인을 무시함.
작업이 token.IsCancellationRequested를 확인하지 않거나 ThrowIfCancellationRequested()를 호출하지 않으면, 취소 시에도 작업이 계속되어 리소스를 낭비합니다.

실수 #2: OperationCanceledException를 잘못 처리함.
이 예외를 잡지 않으면 앱이 예기치 않게 종료될 수 있습니다. 취소 처리를 위해 항상 try-catch를 사용하세요.

실수 #3: 취소 시 리소스 관리 실패.
취소가 변경을 자동으로 롤백하지는 않습니다(예: 파일, DB). catch 블록이나 finally에서 수동으로 정리해야 합니다.

실수 #4: 이미 취소된 토큰 전달.
토큰이 이미 취소된 상태라면 메서드는 즉시 예외를 던질 수 있습니다. 로직에서 이 상황을 고려하세요.

1
설문조사/퀴즈
Task랑 Thread 비교하기, 레벨 60, 레슨 4
사용 불가능
Task랑 Thread 비교하기
데이터 병렬 처리
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION