다음을 통해 공유


작업 목록 취소

완료되기를 기다리지 않으려는 경우 비동기 콘솔 애플리케이션을 취소할 수 있습니다. 이 항목의 예제에 따라 웹 사이트 목록의 콘텐츠를 다운로드하는 애플리케이션에 취소를 추가할 수 있습니다. CancellationTokenSource 인스턴스를 각 태스크와 연결하여 많은 작업을 취소할 수 있습니다. enter 키를 선택하면 아직 완료되지 않은 모든 작업이 취소됩니다.

이 자습서에서는 다음 내용을 다룹니다.

  • .NET 콘솔 애플리케이션 만들기
  • 취소를 지원하는 비동기 애플리케이션 작성
  • 신호 취소 시연

필수 조건

예제 애플리케이션 만들기

새 .NET Core 콘솔 애플리케이션을 만듭니다. dotnet new console 명령을 사용하거나 Visual Studio 에서 만들 수 있습니다. 즐겨 찾는 코드 편집기에서 Program.cs 파일을 엽니다.

지시문을 사용하여 바꾸기

기존 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"
};

CancellationTokenSourceCancellationToken에게 요청된 취소를 알리는 데 사용됩니다. 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 메서드는 이제 실행 파일에 비동기 진입점을 허용하는 비동기 주간주됩니다. 콘솔에 몇 가지 지침 메시지를 쓴 다음 cancelTask이라는 Task 인스턴스를 선언합니다. 그러면 콘솔 키 스트로크를 읽습니다. Enter 키를 누르면 CancellationTokenSource.Cancel() 호출됩니다. 그러면 취소 신호가 표시됩니다. 다음으로 SumPageSizesAsync 메서드에서 sumPageSizesTask 변수가 할당됩니다. 그런 다음 두 작업이 모두 Task.WhenAny(Task[])전달되며 두 작업 중 한 작업이 완료되면 계속됩니다.

다음 코드 블록은 취소가 처리될 때까지 애플리케이션이 종료되지 않도록 합니다. 완료할 첫 번째 작업이 cancelTask경우 sumPageSizeTask 대기합니다. 취소되었을 경우, 기다릴 때 System.Threading.Tasks.TaskCanceledException를 던집니다. 블록은 해당 예외를 catch하고 메시지를 출력합니다.

비동기 합계 페이지 크기 메서드 만들기

Main 메서드 아래에 SumPageSizesAsync 메서드를 추가합니다.

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.TokenProcessUrlAsync 메서드로 전달되고 코드는 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;
    }
}

참고하십시오

다음 단계