CancellationToken에 대한 .NET 프로그래머 가이드

게시 됨: 2022-08-23

때로는 취소하는 것이 좋습니다. 많은 .NET 프로젝트에서 내부 및 외부 프로세스를 모두 취소하려는 동기가 많이 있었습니다. Microsoft는 개발자가 다양한 복잡한 구현에서 이 일반적인 사용 사례에 접근하고 있음을 알게 되었고 더 나은 방법이 있어야 한다고 결정했습니다. 따라서 일반적인 취소 통신 패턴은 낮은 수준의 멀티스레딩 및 프로세스 간 통신 구성을 사용하여 구축된 CancellationToken 으로 도입되었습니다. 이 패턴에 대한 초기 연구의 일환으로 그리고 Microsoft 구현을 위한 실제 .NET 소스 코드를 조사한 후 나는 CancellationToken 이 훨씬 더 광범위한 문제를 해결할 수 있다는 것을 발견했습니다. 트리거 및 플래그를 통한 일반 프로세스 간 통신.

의도된 CancellationToken 사용 사례

CancellationToken 은 작업 취소를 위한 기존 솔루션을 개선하고 표준화하기 위한 수단으로 .NET 4에 도입되었습니다. 인기 있는 프로그래밍 언어가 구현하는 경향이 있는 취소 처리에 대한 네 가지 일반적인 접근 방식이 있습니다.

죽이다 말해봐, 대답을 거절하지마 정중하게 묻고 거절을 받아들인다 플래그를 정중하게 설정하고 원하는 경우 폴링하도록 합니다.
접근하다 하드 스톱; 나중에 불일치 해결 멈추라고 말하지만 일을 정리하게 놔두세요 직접적이지만 부드러운 중지 요청 중지를 요청하되 강요하지 마십시오.
요약 부패와 고통으로 가는 확실한 길 깨끗한 중지 지점을 허용하지만 중지해야 합니다. 깨끗한 중지 지점을 허용하지만 취소 요청은 무시될 수 있습니다. 플래그를 통해 취소 요청
Pthread pthread_kill ,
pthread_cancel (비동기)
pthread_cancel (지연 모드) 해당 사항 없음 깃발을 통해
.그물 Thread.Abort 해당 사항 없음 Thread.Interrupt CancellationToken 의 플래그를 통해
자바 Thread.destroy ,
Thread.stop
해당 사항 없음 Thread.interrupt 플래그 또는 Thread.interrupted 를 통해
파이썬 PyThreadState_SetAsyncExc 해당 사항 없음 asyncio.Task.cancel 깃발을 통해
안내 허용되지 않음 이 접근법을 피하십시오 허용 가능, 특히 언어가 예외 또는 해제를 지원하지 않는 경우 언어가 지원하는 경우 허용 더 좋지만 더 많은 그룹 노력
취소 방법 요약 및 언어 예

CancellationToken 은 취소 대화가 협조적인 최종 범주에 있습니다.

Microsoft가 CancellationToken 을 도입한 후 개발 커뮤니티는 이를 빠르게 수용했습니다. 특히 많은 주요 .NET API가 이러한 토큰을 기본적으로 사용하도록 업데이트되었기 때문입니다. 예를 들어 ASP.NET Core 2.0부터 작업은 HTTP 요청이 닫힌 경우 신호를 보낼 수 있는 선택적 CancellationToken 매개 변수를 지원하므로 모든 작업을 취소할 수 있으므로 리소스의 불필요한 사용을 방지할 수 있습니다.

.NET 코드베이스를 자세히 살펴본 후 CancellationToken 의 사용이 취소에만 국한되지 않는다는 것이 분명해졌습니다.

현미경으로 보는 CancellationToken

CancellationToken 의 구현을 더 자세히 살펴보면 단순한 플래그(예: ManualResetEvent )와 해당 플래그를 모니터링하고 변경할 수 있는 기능을 제공하는 지원 인프라임을 알 수 있습니다. CancellationToken 의 주요 유틸리티는 이름에 있는데, 이는 이것이 작업을 취소하는 일반적인 방법임을 시사합니다. 요즘에는 비동기 또는 장기 실행 작업이 있는 모든 .NET 라이브러리, 패키지 또는 프레임워크에서 이러한 토큰을 통해 취소할 수 있습니다.

CancellationToken 은 플래그를 수동으로 "true"로 설정하거나 특정 시간 범위가 경과한 후 "true"로 변경하도록 프로그래밍하여 트리거할 수 있습니다. CancellationToken 이 트리거되는 방식에 관계없이 이 토큰을 모니터링하는 클라이언트 코드는 다음 세 가지 방법 중 하나를 통해 토큰 플래그의 값을 결정할 수 있습니다.

  • WaitHandle 사용
  • CancellationToken 의 플래그 폴링
  • 플래그 상태가 프로그래밍 방식 구독을 통해 업데이트될 때 클라이언트 코드에 알림

.NET 코드베이스에 대한 추가 연구 후 .NET 팀은 취소와 연결되지 않은 다른 시나리오에서 CancellationTokens 가 유용하다는 것을 알게 되었습니다. 복잡한 상황을 단순화하기 위해 다중 스레드 및 프로세스 간 조정을 통해 C# 개발자에게 권한을 부여하는 이러한 고급 및 브랜드 외 사용 사례 중 일부를 살펴보겠습니다.

고급 이벤트에 대한 CancellationTokens

ASP.NET Core 애플리케이션을 작성할 때 애플리케이션이 시작된 시간을 알아야 하거나 호스트 종료 프로세스에 코드를 삽입해야 하는 경우가 있습니다. 이러한 경우 IHostApplicationLifetime 인터페이스(이전에는 IApplicationLifetime )를 사용합니다. .NET Core의 리포지토리에서 가져온 이 인터페이스는 CancellationToken 을 사용하여 ApplicationStarted , ApplicationStoppingApplicationStopped 의 세 가지 주요 이벤트를 전달합니다.

 namespace Microsoft.Extensions.Hosting { /// <summary> /// Allows consumers to be notified of application lifetime events. /// This interface is not intended to be user-replaceable. /// </summary> public interface IHostApplicationLifetime { /// <summary> /// Triggered when the application host has fully started. /// </summary> CancellationToken ApplicationStarted { get; } /// <summary> /// Triggered when the application host is starting a graceful shutdown. /// Shutdown will block until all callbacks registered on /// this token have completed. /// </summary> CancellationToken ApplicationStopping { get; } /// <summary> /// Triggered when the application host has completed a graceful shutdown. /// The application will not exit until all callbacks registered on /// this token have completed. /// </summary> CancellationToken ApplicationStopped { get; } /// <summary> /// Requests termination of the current application. /// </summary> void StopApplication(); } }

언뜻 보기에 CancellationToken 은 여기에 속하지 않는 것처럼 보일 수 있습니다. 특히 이벤트로 사용되기 때문입니다. 그러나 추가 조사를 통해 다음과 같은 토큰이 완벽하게 적합함을 알 수 있습니다.

  • 인터페이스의 클라이언트가 이러한 이벤트를 수신할 수 있는 다양한 방법을 허용하는 유연합니다.
  • 그들은 상자에서 꺼낸 스레드로부터 안전합니다.
  • CancellationToken 을 결합하여 다른 소스에서 만들 수 있습니다.

CancellationToken 은 모든 이벤트 요구에 완벽하지는 않지만 애플리케이션 시작 또는 중지와 같이 한 번만 발생하는 이벤트에 이상적입니다.

시간 초과에 대한 CancellationToken

기본적으로 ASP.NET은 종료할 시간이 거의 없습니다. 시간이 조금 더 필요한 경우 내장된 HostOptions 클래스를 사용하면 이 시간 초과 값을 변경할 수 있습니다. 그 아래에서 이 시간 초과 값은 CancellationToken 에 래핑되어 기본 하위 프로세스에 제공됩니다.

IHostedServiceStopAsync 메서드는 이 사용법의 좋은 예입니다.

 namespace Microsoft.Extensions.Hosting { /// <summary> /// Defines methods for objects that are managed by the host. /// </summary> public interface IHostedService { /// <summary> /// Triggered when the application host is ready to start the service. /// </summary> /// <param name="cancellationToken">Indicates that the start /// process has been aborted.</param> Task StartAsync(CancellationToken cancellationToken); /// <summary> /// Triggered when the application host is performing a graceful shutdown. /// </summary> /// <param name="cancellationToken">Indicates that the shutdown /// process should no longer be graceful.</param> Task StopAsync(CancellationToken cancellationToken); } }

IHostedService 인터페이스 정의에서 알 수 있듯이 StopAsync 메서드는 하나의 CancellationToken 매개 변수를 사용합니다. 해당 매개변수와 관련된 주석은 CancellationToken 에 대한 Microsoft의 초기 의도가 취소 프로세스가 아니라 시간 초과 메커니즘임을 명확하게 전달합니다.

내 생각에 이 인터페이스가 CancellationToken 의 존재 이전에 존재했다면 중지 작업이 처리될 수 있는 기간을 나타내는 TimeSpan 매개변수일 수 있습니다. 내 경험에 따르면 시간 초과 시나리오는 거의 항상 훌륭한 추가 유틸리티를 사용하여 CancellationToken 으로 변환할 수 있습니다.

잠시 동안 StopAsync 메서드가 어떻게 설계되었는지 알고 있다는 사실을 잊고 대신 이 메서드의 계약을 설계하는 방법에 대해 생각합시다. 먼저 요구 사항을 정의하겠습니다.

  • StopAsync 메서드는 서비스를 중지해야 합니다.
  • StopAsync 메서드에는 정상적인 중지 상태가 있어야 합니다.
  • 정상적인 중지 상태에 도달했는지 여부에 관계없이 호스팅된 서비스에는 timeout 매개변수에 정의된 대로 중지할 최대 시간이 있어야 합니다.

어떤 형태 StopAsync 메서드를 사용하여 첫 번째 요구 사항을 충족합니다. 나머지 요구 사항은 까다롭습니다. CancellationToken 은 표준 .NET 플래그 기반 통신 도구를 사용하여 대화를 강화함으로써 이러한 요구 사항을 정확히 충족합니다.

알림 메커니즘으로서의 CancellationToken

CancellationToken 의 가장 큰 비밀은 그것이 단지 플래그라는 것입니다. CancellationToken 을 사용하여 프로세스를 중지하는 대신 시작하는 방법을 설명하겠습니다.

다음을 고려하세요:

  1. RandomWorker 클래스를 만듭니다.
  2. RandomWorker 에는 임의의 작업을 실행하는 DoWorkAsync 메서드가 있어야 합니다.
  3. DoWorkAsync 메서드는 호출자가 작업을 시작해야 하는 시간을 지정할 수 있도록 해야 합니다.
 public class RandomWorker { public RandomWorker(int id) { Id = id; } public int Id { get; } public async Task DoWorkAsync() { for (int i = 1; i <= 10; i++) { Console.WriteLine($"[Worker {Id}] Iteration {i}"); await Task.Delay(1000); } } }

위의 클래스는 처음 두 가지 요구 사항을 충족하고 세 번째 요구 사항을 남깁니다. 시간 범위 또는 간단한 플래그와 같이 작업자를 트리거하는 데 사용할 수 있는 몇 가지 대체 인터페이스가 있습니다.

 # With a time span Task DoWorkAsync(TimeSpan startAfter); # Or a simple flag bool ShouldStart { get; set; } Task DoWorkAsync();

이 두 가지 접근 방식은 괜찮지만 CancellationToken 을 사용하는 것만큼 우아한 것은 없습니다.

 public class RandomWorker { public RandomWorker(int id) { Id = id; } public int Id { get; } public async Task DoWorkAsync(CancellationToken startToken) { startToken.WaitHandle.WaitOne(); for (int i = 1; i <= 10; i++) { Console.WriteLine($"[Worker {Id}] Iteration {i}"); await Task.Delay(1000); } } }

이 샘플 클라이언트 코드는 이 디자인의 힘을 보여줍니다.

 using System; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace CancelToStart { public class Program { static void Main(string[] args) { CancellationTokenSource startCts = new CancellationTokenSource(); startCts.CancelAfter(TimeSpan.FromSeconds(10)); var tasks = Enumerable.Range(0, 10) .Select(i => new RandomWorker(i)) .Select(worker => worker.DoWorkAsync(startCts.Token)) .ToArray(); Task.WaitAll(tasks, CancellationToken.None); } } }

CancellationTokenSource 는 배후에서 CancellationToken 을 만들고 모든 관련 프로세스의 트리거를 조정합니다. 이 경우 관련 프로세스는 시작을 기다리고 있는 RandomWorker 입니다. 이 접근 방식을 통해 기본 CancellationToken 구현에 포함된 스레드 안전성을 활용할 수 있습니다.

광범위한 CancellationToken 도구 상자

이러한 예는 CancellationToken 이 의도한 사용 사례 외에 유용한 솔루션의 도구 상자를 제공하는 방법을 보여줍니다. 이 도구는 프로세스 간 플래그 기반 통신과 관련된 많은 시나리오에서 유용할 수 있습니다. 시간 초과, 알림 또는 일회성 이벤트가 발생하더라도 Microsoft에서 테스트한 이 우아한 구현으로 대체할 수 있습니다.

위에서 아래로 "Gold"(금색), "Microsoft" 및 "Partner"(둘 다 검은색)라는 단어가 표시되고 그 뒤에 Microsoft 로고가 표시됩니다.
Microsoft 골드 파트너인 Toptal은 Microsoft 전문가들로 구성된 엘리트 네트워크입니다. 언제 어디서나 필요한 전문가와 함께 고성능 팀을 구성하세요!

Toptal 엔지니어링 블로그에 대한 추가 정보:

  • .NET Core: 야생 및 오픈 소스로의 전환. 마이크로소프트, 왜 이렇게 오래 걸렸어?!
  • ASP.NET Core를 사용하여 ASP.NET Web API 빌드
  • .NET 프로젝트를 부트스트랩하고 생성하는 방법