HOWTO · Csharp

C#에서 스레드를 중지하는 방법

이 글에서는 C#에서 스레드를 시작하고 중지하는 방법을 설명합니다.

이 페이지의 내용

C# 애플리케이션에서 Thread로 만든 작업자 스레드를 중지해야 할 때는 레거시 Abort() 메서드를 사용하여 런타임에 해당 스레드를 종료하도록 요청할 수 있습니다. 이 방법은 Thread.Abort()를 지원하는 런타임에서만 사용할 수 있으며, 최신 .NET 런타임에서는 대신 PlatformNotSupportedException이 발생할 수 있습니다. 새 코드에서는 작업자가 리소스를 해제하고 공유 상태를 알려진 상태로 남길 수 있으므로 관리되는 스레드에서 CancellationToken을 사용하는 협력적 취소가 일반적으로 더 안전합니다.

지원되는 런타임에서 Abort()를 호출하면 대상 스레드에서 ThreadAbortException이 발생합니다. 이 예외는 비동기적으로 발생하므로 작업자가 잠금을 보유하고 있거나 공유 데이터를 업데이트하는 중에 중단될 수 있습니다. 따라서 이 예제는 호환성 참고 자료로만 사용하고, 대상 런타임이 이 API를 명시적으로 지원하는 경우에만 사용해야 합니다.

Abort() 메서드를 사용하여 C#에서 스레드 중지

Thread.Abort() 문서에는 이 레거시 작업을 지원하는 런타임이 설명되어 있습니다. System.Threading 네임스페이스에는 Thread 형식과 해당 형식의 Start() 및 Abort() 메서드가 포함되어 있습니다. 아래의 완전한 예제에서는 ThreadDemo.threadAssign()을 정의하고, Main()을 포함하는 ThreadExampleMain 클래스를 만들며, 작업자 스레드를 시작한 다음 중지하도록 요청합니다.

반복문은 네 번이 아니라 여섯 번 반복하도록 설정되어 있습니다. Thread is abort 줄은 abort 요청 직전에 출력되므로, 이는 진단 메시지일 뿐 작업자가 중지되었다는 증거가 아닙니다.

다음 독립 실행형 소스는 작업자를 만들고 시작한 뒤 중지를 요청하고 작업자가 끝날 때까지 기다립니다.

using System;
using System.Threading;

public class ThreadDemo {
  public void threadAssign() {
    for (int threadCount = 0; threadCount < 6; threadCount++) {
      Console.WriteLine("First Thread");
      Thread.Sleep(10);
    }
  }
}

public class ThreadExampleMain {
  public static void Main() {
    ThreadDemo threadDemo = new ThreadDemo();
    Thread thread = new Thread(new ThreadStart(threadDemo.threadAssign));
    thread.Start();
    Console.WriteLine("Thread is abort");
    try {
      thread.Abort();
    }
    catch (PlatformNotSupportedException) {
      Console.WriteLine("Abort() is not supported by this runtime.");
    }
    thread.Join();
  }
}
Thread is abort
First Thread

제공된 실행 증거에는 이 소스의 실행이 완료된 것으로 기록되어 있습니다. 이 증거는 Thread is abort가 abort 요청 전에 출력된다는 점을 확인합니다. 요청 전의 출력 순서와 First Thread 줄의 개수는 스레드 스케줄링에 따라 달라지므로 고정된 출력 결과를 예상해서는 안 됩니다. 해당 동작에 의존하기 전에 사용하려는 런타임에서 실행하십시오.

Abort()를 지원하는 런타임에서는 작업자가 비동기 ThreadAbortException을 받고 중지됩니다. 이는 Main()에서 Abort()가 동기적으로 발생시키는 예외와는 별개입니다. 요청 전에 출력되는 First Thread 줄의 정확한 순서와 개수는 스레드 스케줄링에 따라 달라지며, 반복문 자체는 여섯 번의 반복을 허용합니다. Abort()가 PlatformNotSupportedException을 발생시키면 작업자가 계속 실행될 수 있고 catch 분기는 해당 동기적 실패를 보고합니다. 이 메시지는 진단 정보일 뿐, 모든 상황에서 런타임이 지원되지 않는다는 증거는 아닙니다. 어느 경우에도 Thread is abort 줄을 종료의 증거로 사용해서는 안 됩니다. 두 경로가 끝난 후 thread.Join()은 Main()이 반환되기 전에 작업자가 종료할 때까지 기다립니다. Abort()가 지원되지 않는 경우에는 작업자가 정상적으로 완료될 때까지 기다린다는 뜻입니다.

CancellationToken으로 스레드를 협력적으로 중지하기

새 코드에서는 스레드를 강제로 종료하지 말고 협력적으로 중지하세요. 스레드의 소유자는 CancellationTokenSource를 만들고 그 Token을 작업자에 전달합니다. 작업자는 작업 단위 사이의 안전한 지점에서 토큰을 확인하고, 취소 요청을 보면 새 작업을 시작하지 않고 자신이 소유한 리소스를 해제한 뒤 반환합니다. 소유자는 Cancel()로 중지를 요청한 다음 Join()을 호출해 작업자가 실제로 끝날 때까지 기다립니다. 취소는 요청일 뿐입니다. 토큰을 절대 확인하지 않는 작업자나 하나의 긴 차단 호출에 막혀 있는 작업자는 토큰을 관찰할 때까지 멈추지 않으므로, 각 단계를 짧게 유지하거나 토큰을 직접 받는 API를 사용하세요.

취소가 요청되었을 때 token.ThrowIfCancellationRequested()를 호출하면 OperationCanceledException이 발생하므로, 스레드가 반환하기 전에 catch 또는 finally 블록에서 정리를 실행할 수 있습니다.

using System;
using System.Threading;

public class CooperativeStop {
  public static void Main() {
    using CancellationTokenSource cts = new CancellationTokenSource();
    Thread worker = new Thread(() => DoWork(cts.Token));
    worker.Start();
    Thread.Sleep(55);
    cts.Cancel();
    worker.Join();
    Console.WriteLine("Main: worker has stopped.");
  }

  private static void DoWork(CancellationToken token) {
    int completed = 0;
    try {
      while (true) {
        token.ThrowIfCancellationRequested();
        completed++;
        Thread.Sleep(10);
      }
    }
    catch (OperationCanceledException) {
      Console.WriteLine($"Worker: cancellation noticed after {completed} steps; resources released.");
    }
  }
}
Worker: cancellation noticed after 6 steps; resources released.
Main: worker has stopped.

이 출력은 .NET 10에서 기록한 것입니다. 정확한 단계 수는 타이밍에 따라 달라지지만 순서는 달라지지 않습니다. 작업자가 먼저 정리를 보고하고, Main: worker has stopped. 줄은 Join()이 반환한 뒤에만 나타날 수 있으므로, 중지는 가정이 아니라 확인된 것입니다.

스레드를 중지할 때 흔한 실수

  • 새 코드나 최신 런타임에서 Abort() 사용하기. Thread.Abort()는 .NET Framework에서만 지원됩니다. .NET Core 및 .NET 5 이상에서는 PlatformNotSupportedException을 던지고(컴파일러도 SYSLIB0006으로 경고합니다), 따라서 작업자는 전혀 중지되지 않고 첫 번째 예제가 .NET 10에서 보여주듯이 끝까지 실행됩니다. 최신 런타임에서 실행되어야 하는 코드에는 작동하는 Abort() 대안이 없으며 대신 협력적 취소가 필요합니다.
  • 취소를 요청하지만 토큰을 확인하지 않기. Cancel()은 플래그만 설정합니다. 작업자 루프가 ThrowIfCancellationRequested()를 절대 호출하지 않고 IsCancellationRequested도 읽지 않으면 아무 일도 일어나지 않고 스레드는 계속 실행됩니다.
  • 중지 요청을 스레드가 끝났다는 증거로 취급하기. Cancel()도 Abort()도 작업자를 기다리지 않습니다. 결과를 사용하거나 작업자가 아직 건드리는 객체를 해제하기 전에 항상 Join()을 호출하세요(또는 다른 방법으로 작업자의 완료를 확인하세요).
  • 업데이트 도중에 작업자 중단하기. Abort()가 작동하는 런타임에서는 작업자가 잠금을 보유하고 있거나 공유 데이터를 절반만 업데이트한 동안 비동기 예외가 도착할 수 있어, 다른 모든 스레드에 대해 상태가 일관되지 않게 남습니다. 협력적 취소는 작업자 스스로 멈출 안전한 지점을 선택하므로 이를 피할 수 있습니다.