작업 목록 취소
완료될 때까지 기다리지 않으려는 경우 비동기 콘솔 애플리케이션을 취소할 수 있습니다. 이 항목의 예제에 따라 웹 사이트 목록의 콘텐츠를 다운로드하는 애플리케이션에 취소를 추가할 수 있습니다. CancellationTokenSource 인스턴스를 각 작업과 연결하여 많은 작업을 취소할 수 있습니다. Enter 키를 선택하면 아직 완료되지 않은 모든 작업이 취소됩니다.
이 자습서에서는 다음 내용을 다룹니다.
- .NET 콘솔 애플리케이션 만들기
- 취소를 지원하는 비동기 애플리케이션 작성
- 취소 신호 보내기 시연
필수 조건
이 자습서를 사용하려면 다음이 필요합니다.
- .NET 5 이상 SDK
- IDE(통합 개발 환경)
예제 애플리케이션 만들기
새 .NET Core 콘솔 애플리케이션을 만듭니다. dotnet new console
명령 또는 Visual Studio를 사용하여 만들 수 있습니다. 선호하는 코드 편집기에서 Program.cs 파일을 엽니다.
using 문 바꾸기
기존 using 문을 다음 선언으로 바꿉니다.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
필드 추가
Program
클래스 정의에서 다음 세 필드를 추가합니다.
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();
static readonly HttpClient s_client = new HttpClient
{
MaxResponseContentBufferSize = 1_000_000
};
static readonly IEnumerable<string> s_urlList = new string[]
{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/maui"
};
CancellationTokenSource는 요청된 취소를 CancellationToken에 신호로 보내는 데 사용됩니다. HttpClient
는 HTTP 요청을 보내고 HTTP 응답을 받는 기능을 공개합니다. s_urlList
는 애플리케이션이 처리해야 하는 모든 URL을 저장합니다.
애플리케이션 진입점 업데이트
콘솔 애플리케이션의 주 진입점은 Main
메서드입니다. 기존 메서드를 다음으로 바꿉니다.
static async Task Main()
{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");
Task cancelTask = Task.Run(() =>
{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}
Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
s_cts.Cancel();
});
Task sumPageSizesTask = SumPageSizesAsync();
Task finishedTask = await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });
if (finishedTask == cancelTask)
{
// wait for the cancellation to take place:
try
{
await sumPageSizesTask;
Console.WriteLine("Download task completed before cancel request was processed.");
}
catch (TaskCanceledException)
{
Console.WriteLine("Download task has been cancelled.");
}
}
Console.WriteLine("Application ending.");
}
업데이트된 Main
메서드는 이제 Async main으로 간주되어 실행 파일에 대한 비동기 진입점을 허용합니다. 콘솔에 몇 가지 지침 메시지를 기록한 다음, cancelTask
라는 Task 인스턴스를 선언합니다. 그러면 콘솔 키 입력이 읽힙니다. Enter 키를 누르면 CancellationTokenSource.Cancel()이 호출됩니다. 그러면 취소 신호가 전송됩니다. 다음으로, sumPageSizesTask
변수가 SumPageSizesAsync
메서드에서 할당됩니다. 두 작업은 모두 Task.WhenAny(Task[])에 전달되며 해당 항목은 두 작업 중 하나가 완료되면 계속됩니다.
다음 코드 블록은 취소가 처리될 때까지 애플리케이션이 종료되지 않도록 합니다. 완료할 첫 번째 작업이 cancelTask
인 경우 sumPageSizeTask
이(가) 대기합니다. 취소된 경우 대기하면 System.Threading.Tasks.TaskCanceledException이(가) 반환됩니다. 블록은 해당 예외를 catch하고 메시지를 출력합니다.
비동기 합계 페이지 크기 메서드 만들기
SumPageSizesAsync
메서드 아래에 Main
메서드를 추가합니다.
static async Task SumPageSizesAsync()
{
var stopwatch = Stopwatch.StartNew();
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
Console.WriteLine($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}
Stopwatch를 인스턴스화하고 시작함으로써 메서드가 시작됩니다. 그런 다음, s_urlList
의 각 URL을 반복하고 ProcessUrlAsync
를 호출합니다. 각 반복에서 s_cts.Token
은 ProcessUrlAsync
메서드에 전달되며 코드는 Task<TResult>를 반환합니다. 여기서 TResult
는 정수입니다.
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
프로세스 메서드 추가
SumPageSizesAsync
메서드 아래에 다음 ProcessUrlAsync
메서드를 추가합니다.
static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)
{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
지정된 URL에서 메서드는 제공된 client
인스턴스를 사용하여 응답을 byte[]
로 가져옵니다. CancellationToken 인스턴스는 HttpClient.GetAsync(String, CancellationToken) 및 HttpContent.ReadAsByteArrayAsync() 메서드에 전달됩니다. token
은 요청된 취소를 등록하는 데 사용됩니다. URL 및 길이가 콘솔에 기록된 후 길이가 반환됩니다.
예제 애플리케이션 출력
Application started.
Press the ENTER key to cancel...
https://learn.microsoft.com 37,357
https://learn.microsoft.com/aspnet/core 85,589
https://learn.microsoft.com/azure 398,939
https://learn.microsoft.com/azure/devops 73,663
https://learn.microsoft.com/dotnet 67,452
https://learn.microsoft.com/dynamics365 48,582
https://learn.microsoft.com/education 22,924
ENTER key pressed: cancelling downloads.
Application ending.
전체 예제
다음 코드는 예제에 관한 Program.cs 파일의 전체 텍스트입니다.
using System.Diagnostics;
class Program
{
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();
static readonly HttpClient s_client = new HttpClient
{
MaxResponseContentBufferSize = 1_000_000
};
static readonly IEnumerable<string> s_urlList = new string[]
{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/maui"
};
static async Task Main()
{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");
Task cancelTask = Task.Run(() =>
{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}
Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
s_cts.Cancel();
});
Task sumPageSizesTask = SumPageSizesAsync();
Task finishedTask = await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });
if (finishedTask == cancelTask)
{
// wait for the cancellation to take place:
try
{
await sumPageSizesTask;
Console.WriteLine("Download task completed before cancel request was processed.");
}
catch (OperationCanceledException)
{
Console.WriteLine("Download task has been cancelled.");
}
}
Console.WriteLine("Application ending.");
}
static async Task SumPageSizesAsync()
{
var stopwatch = Stopwatch.StartNew();
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
Console.WriteLine($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}
static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)
{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
}
참고 항목
다음 단계
.NET