다음을 통해 공유


비동기 작업 하나가 완료되면 남은 비동기 작업 취소(C# 및 Visual Basic)

CancellationToken와 함께 Task.WhenAny 메서드를 사용하여 작업이 하나 완료되는 시점에서 나머지 모든 작업을 취소할 수 있습니다. WhenAny 메서드는 작업 컬렉션인 인수를 사용합니다. 메서드는 모든 작업을 시작하고 단일 작업을 반환합니다. 컬렉션에 있는 일련의 작업이 완료되면 단일 작업이 완료됩니다.

이 예제에서는 WhenAny로 연속 작업의 취소 토큰을 사용하여 작업 컬렉션에서 완료할 첫 번째 작업을 유지하고 남은 작업을 취소하는 방법을 보여 줍니다. 각 작업은 웹 사이트의 콘텐츠를 다운로드합니다. 이 예제에서는 완료할 첫 번째 다운로드 내용의 길이를 표시하고 다른 다운로드를 취소합니다.

참고

예제를 실행하려면 Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 for Windows Desktop, Visual Studio Express 2013 for Windows 또는 .NET Framework 4.5나 4.5.1이 컴퓨터에 설치되어 있어야 합니다.

예제 다운로드

Async 샘플: 응용 프로그램 미세 조정에서 전체 WPF(Windows Presentation Foundation) 프로젝트를 다운로드하고 다음 단계를 수행합니다.

  1. 다운로드한 파일을 압축한 다음 Visual Studio를 시작합니다.

  2. 메뉴 모음에서 파일, 열기, 프로젝트/솔루션을 선택합니다.

  3. 프로젝트 열기 대화 상자에서 압축한 샘플 코드가 있는 폴더를 연 다음 AsyncFineTuningCS 또는 AsyncFineTuningVB에 대한 솔루션 파일(.sln)을 엽니다.

  4. 솔루션 탐색기에서 CancelAfterOneTask 프로젝트에 대한 바로 가기 메뉴를 연 후 시작 프로젝트로 설정을 선택합니다.

  5. F5 키를 선택하여 프로젝트를 실행합니다.

    디버깅하지 않고 프로젝트를 실행하려면 Ctrl+F5 키를 선택합니다.

  6. 프로그램을 여러 번 실행하여 다른 다운로드가 먼저 종료되는지 확인합니다.

프로젝트를 다운로드하지 않으려면 항목의 끝에서 MainWindow.xaml.vb 및 MainWindow.xaml.cs 파일을 검토할 수 있습니다.

예제 빌드

이 항목의 예제에서는 작업 목록을 취소하기 위해 비동기 작업 또는 작업 목록 취소(C# 및 Visual Basic)에서 개발된 프로젝트에 추가합니다. 취소 단추가 명시적으로 사용되지 않지만 이 예제는 동일한 UI를 사용합니다.

예제를 스스로 단계별로 빌드하려면 "예제 다운로드" 단원의 지침을 따르지만 시작 프로젝트CancelAListOfTasks를 선택하십시오. 이 항목의 변경 사항을 해당 프로젝트에 추가합니다.

CancelAListOfTasks 프로젝트의 MainWindow.xaml.vb 또는 MainWindow.xaml.cs 파일에서 각 웹사이트에 대한 처리 단계를 AccessTheWebAsync의 루프에서 다음 비동기 메서드로 이동하여 전환을 시작합니다.

' ***Bundle the processing steps for a website into one async method.
Async Function ProcessURLAsync(url As String, client As HttpClient, ct As CancellationToken) As Task(Of Integer)

    ' GetAsync returns a Task(Of HttpResponseMessage).  
    Dim response As HttpResponseMessage = Await client.GetAsync(url, ct)

    ' Retrieve the website contents from the HttpResponseMessage. 
    Dim urlContents As Byte() = Await response.Content.ReadAsByteArrayAsync()

    Return urlContents.Length
End Function
// ***Bundle the processing steps for a website into one async method.
async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
{
    // GetAsync returns a Task<HttpResponseMessage>. 
    HttpResponseMessage response = await client.GetAsync(url, ct);

    // Retrieve the website contents from the HttpResponseMessage. 
    byte[] urlContents = await response.Content.ReadAsByteArrayAsync();

    return urlContents.Length;
}

AccessTheWebAsync에서 이 예제는 쿼리인 ToArray``1 메서드 및 WhenAny 메서드를 사용하여 작업 배열을 만들고 시작합니다. WhenAny의 응용 프로그램은 대기할 경우 작업 배열에서 배열이 단일 작업을 반환하고 완료에 도달하는 첫 번째 작업을 평가합니다.

AccessTheWebAsync에서 다음과 같이 변경합니다. 별표는 코드 파일의 변경 내용을 표시합니다.

  1. 루프를 주석으로 처리하거나 삭제합니다.

  2. 실행 시 제네릭 작업의 컬렉션을 만들 쿼리를 만듭니다. ProcessURLAsync에 대해 호출할 때마다 Task이 반환되며, 여기서 TResult는 정수입니다.

    ' ***Create a query that, when executed, returns a collection of tasks. 
    Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) =
        From url In urlList Select ProcessURLAsync(url, client, ct)
    
    // ***Create a query that, when executed, returns a collection of tasks.
    IEnumerable<Task<int>> downloadTasksQuery =
        from url in urlList select ProcessURLAsync(url, client, ct);
    
  3. ToArray를 호출하여 쿼리를 실행하고 작업을 시작합니다. 다음 단계의 WhenAny 메서드의 응용 프로그램은 ToArray를 사용하지 않고 쿼리를 실행하고 작업을 시작하지만, 다른 메서드는 그렇지 않을 수도 있습니다. 가장 안전한 방법은 명시적으로 쿼리를 강제로 실행하는 것입니다.

    ' ***Use ToArray to execute the query and start the download tasks.  
    Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
    
    // ***Use ToArray to execute the query and start the download tasks. 
    Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
    
  4. 작업 컬렉션에서 WhenAny를 호출합니다. WhenAny은 Task(Of Task(Of Integer)) 또는 Task<Task<int>>를 반환합니다. 즉, WhenAny은 대기할 경우 Task(Of Integer) 또는 단일 Task<int>으로 평가하는 작업을 반환합니다. 컬렉션에서 이 단일 작업이 가장 먼저 완료됩니다. 먼저 완료된 작업은 firstFinishedTask에 할당됩니다. firstFinishedTask의 형식은 Task입니다. 여기서 TResult는 ProcessURLAsync의 반환 형식이므로 정수입니다.

    ' ***Call WhenAny and then await the result. The task that finishes  
    ' first is assigned to firstFinishedTask. 
    Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
    
    // ***Call WhenAny and then await the result. The task that finishes  
    // first is assigned to firstFinishedTask.
    Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
    
  5. 이 예제에서는 먼저 완료되는 작업만 필요합니다. 따라서 CancellationTokenSource.Cancel을 사용하여 나머지 작업을 취소합니다.

    ' ***Cancel the rest of the downloads. You just want the first one.
    cts.Cancel()
    
    // ***Cancel the rest of the downloads. You just want the first one.
    cts.Cancel();
    
  6. 마지막으로 firstFinishedTask를 기다려 다운로드한 콘텐츠의 길이를 검색합니다.

    Dim length = Await firstFinishedTask
    resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website:  {0}" & vbCrLf, length)
    
    var length = await firstFinishedTask;
    resultsTextBox.Text += String.Format("\r\nLength of the downloaded website:  {0}\r\n", length);
    

프로그램을 여러 번 실행하여 다른 다운로드가 먼저 종료되는지 확인합니다.

완성된 예제

다음 코드는 예제의 전체 MainWindow.xaml.vb 또는 MainWindow.xaml.cs 파일입니다. 별표는 이 예제에 추가된 요소를 표시합니다.

System.Net.Http에 대한 참조를 추가해야 합니다.

Async 샘플: 응용 프로그램 미세 조정에서 프로젝트를 다운로드할 수 있습니다.

' Add an Imports directive and a reference for System.Net.Http. 
Imports System.Net.Http

' Add the following Imports directive for System.Threading. 
Imports System.Threading

Class MainWindow

    ' Declare a System.Threading.CancellationTokenSource. 
    Dim cts As CancellationTokenSource


    Private Async Sub startButton_Click(sender As Object, e As RoutedEventArgs)

        ' Instantiate the CancellationTokenSource.
        cts = New CancellationTokenSource()

        resultsTextBox.Clear()

        Try
            Await AccessTheWebAsync(cts.Token)
            resultsTextBox.Text &= vbCrLf & "Download complete." 

        Catch ex As OperationCanceledException
            resultsTextBox.Text &= vbCrLf & "Download canceled." & vbCrLf

        Catch ex As Exception
            resultsTextBox.Text &= vbCrLf & "Download failed." & vbCrLf
        End Try 

        ' Set the CancellationTokenSource to Nothing when the download is complete.
        cts = Nothing 
    End Sub 


    ' You can still include a Cancel button if you want to. 
    Private Sub cancelButton_Click(sender As Object, e As RoutedEventArgs)

        If cts IsNot Nothing Then
            cts.Cancel()
        End If 
    End Sub 


    ' Provide a parameter for the CancellationToken. 
    ' Change the return type to Task because the method has no return statement.
    Async Function AccessTheWebAsync(ct As CancellationToken) As Task

        Dim client As HttpClient = New HttpClient()

        ' Call SetUpURLList to make a list of web addresses. 
        Dim urlList As List(Of String) = SetUpURLList()

        '' Comment out or delete the loop. 
        ''For Each url In urlList 
        ''    ' GetAsync returns a Task(Of HttpResponseMessage).  
        ''    ' Argument ct carries the message if the Cancel button is chosen.  
        ''    ' Note that the Cancel button can cancel all remaining downloads. 
        ''    Dim response As HttpResponseMessage = Await client.GetAsync(url, ct) 

        ''    ' Retrieve the website contents from the HttpResponseMessage. 
        ''    Dim urlContents As Byte() = Await response.Content.ReadAsByteArrayAsync() 

        ''    resultsTextBox.Text &= 
        ''        String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, urlContents.Length) 
        ''Next 

        ' ***Create a query that, when executed, returns a collection of tasks. 
        Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) =
            From url In urlList Select ProcessURLAsync(url, client, ct)

        ' ***Use ToArray to execute the query and start the download tasks.  
        Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()

        ' ***Call WhenAny and then await the result. The task that finishes  
        ' first is assigned to firstFinishedTask. 
        Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)

        ' ***Cancel the rest of the downloads. You just want the first one.
        cts.Cancel()

        ' ***Await the first completed task and display the results 
        ' Run the program several times to demonstrate that different 
        ' websites can finish first. 
        Dim length = Await firstFinishedTask
        resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website:  {0}" & vbCrLf, length)
    End Function 


    ' ***Bundle the processing steps for a website into one async method.
    Async Function ProcessURLAsync(url As String, client As HttpClient, ct As CancellationToken) As Task(Of Integer)

        ' GetAsync returns a Task(Of HttpResponseMessage).  
        Dim response As HttpResponseMessage = Await client.GetAsync(url, ct)

        ' Retrieve the website contents from the HttpResponseMessage. 
        Dim urlContents As Byte() = Await response.Content.ReadAsByteArrayAsync()

        Return urlContents.Length
    End Function 


    ' Add a method that creates a list of web addresses. 
    Private Function SetUpURLList() As List(Of String)

        Dim urls = New List(Of String) From
            {
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            }
        Return urls
    End Function 

End Class 


' Sample output: 

' Length of the downloaded website:  158856 

' Download complete.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

// Add a using directive and a reference for System.Net.Http. 
using System.Net.Http;

// Add the following using directive. 
using System.Threading;

namespace CancelAfterOneTask
{
    public partial class MainWindow : Window
    {
        // Declare a System.Threading.CancellationTokenSource.
        CancellationTokenSource cts;

        public MainWindow()
        {
            InitializeComponent();
        }


        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            // Instantiate the CancellationTokenSource.
            cts = new CancellationTokenSource();

            resultsTextBox.Clear();

            try
            {
                await AccessTheWebAsync(cts.Token);
                resultsTextBox.Text += "\r\nDownload complete.";
            }
            catch (OperationCanceledException)
            {
                resultsTextBox.Text += "\r\nDownload canceled.";
            }
            catch (Exception)
            {
                resultsTextBox.Text += "\r\nDownload failed.";
            }

            // Set the CancellationTokenSource to null when the download is complete.
            cts = null;
        }


        // You can still include a Cancel button if you want to. 
        private void cancelButton_Click(object sender, RoutedEventArgs e)
        {
            if (cts != null)
            {
                cts.Cancel();
            }
        }


        // Provide a parameter for the CancellationToken.
        async Task AccessTheWebAsync(CancellationToken ct)
        {
            HttpClient client = new HttpClient();

            // Call SetUpURLList to make a list of web addresses.
            List<string> urlList = SetUpURLList();

            // ***Comment out or delete the loop. 
            //foreach (var url in urlList) 
            //{ 
            //    // GetAsync returns a Task<HttpResponseMessage>.  
            //    // Argument ct carries the message if the Cancel button is chosen.  
            //    // ***Note that the Cancel button can cancel all remaining downloads. 
            //    HttpResponseMessage response = await client.GetAsync(url, ct); 

            //    // Retrieve the website contents from the HttpResponseMessage. 
            //    byte[] urlContents = await response.Content.ReadAsByteArrayAsync(); 

            //    resultsTextBox.Text += 
            //        String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length);
            //} 

            // ***Create a query that, when executed, returns a collection of tasks.
            IEnumerable<Task<int>> downloadTasksQuery =
                from url in urlList select ProcessURLAsync(url, client, ct);

            // ***Use ToArray to execute the query and start the download tasks. 
            Task<int>[] downloadTasks = downloadTasksQuery.ToArray();

            // ***Call WhenAny and then await the result. The task that finishes  
            // first is assigned to firstFinishedTask.
            Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);

            // ***Cancel the rest of the downloads. You just want the first one.
            cts.Cancel();

            // ***Await the first completed task and display the results.  
            // Run the program several times to demonstrate that different 
            // websites can finish first. 
            var length = await firstFinishedTask;
            resultsTextBox.Text += String.Format("\r\nLength of the downloaded website:  {0}\r\n", length);
        }


        // ***Bundle the processing steps for a website into one async method.
        async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
        {
            // GetAsync returns a Task<HttpResponseMessage>. 
            HttpResponseMessage response = await client.GetAsync(url, ct);

            // Retrieve the website contents from the HttpResponseMessage. 
            byte[] urlContents = await response.Content.ReadAsByteArrayAsync();

            return urlContents.Length;
        }


        // Add a method that creates a list of web addresses. 
        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            };
            return urls;
        }
    }
    // Sample output: 

    // Length of the downloaded website:  158856 

    // Download complete.
}

참고 항목

참조

WhenAny

개념

Async 응용 프로그램 미세 조정(C# 및 Visual Basic)

Async 및 Await를 사용한 비동기 프로그래밍(C# 및 Visual Basic)

기타 리소스

Async 샘플: 응용 프로그램 미세 조정