다음을 통해 공유


방법: 여러 개의 취소 요청 수신 대기

이 예제에서는 둘 중 한 토큰의 요청이 있을 경우 작업을 취소할 수 있도록 두 개의 취소 토큰을 동시에 수신하는 방법을 보여 줍니다.

참고참고

"내 코드만"을 사용하는 경우 Visual Studio에서는 예외를 throw하는 줄에서 실행을 중단하고 예외가 사용자 코드를 통해 처리되지 않았음을 알리는 오류 메시지를 표시할 수 있습니다. 이는 크게 심각한 오류는 아닙니다.F5 키를 눌러 중단된 지점부터 실행을 계속하고 아래 예제에 나와 있는 것과 같은 예외 처리 동작을 확인할 수 있습니다.맨 처음 오류 지점에서 Visual Studio가 실행을 중단하지 않도록 하려면 도구, 옵션, 디버깅, 일반을 차례로 선택하고 "내 코드만" 확인란의 선택을 취소하기만 하면 됩니다.

예제

다음 예제에서는 [M:M:System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken[])] 메서드를 사용하여 두 개의 토큰을 하나의 토큰으로 조인합니다. 이렇게 하면 하나의 취소 토큰만 인수로 사용하는 메서드에 토큰을 전달할 수 있습니다. 이 예제에서는 메서드가 클래스 밖에서 전달된 토큰과 클래스 안에서 생성된 토큰을 둘 다 관찰하는 일반적인 시나리오를 보여 줍니다.

Class LinkedTokenSourceDemo

    Shared Sub Main()

        Dim worker As New WorkerWithTimer()
        Dim cts As New CancellationTokenSource()

        ' Task for UI thread, so we can call Task.Wait wait on the main thread.
        Task.Factory.StartNew(Sub()

                                  Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.")
                                  Console.WriteLine("Or let the task time out by doing nothing.")
                                  If Console.ReadKey().KeyChar = "c"c Then
                                      cts.Cancel()
                                  End If
                              End Sub
    )
        ' Let the user read the UI message.
        Thread.Sleep(1000)

        ' Start the worker task.
        Dim t As Task = Task.Factory.StartNew(Sub() worker.DoWork(cts.Token), cts.Token)

        Try

            t.Wait()


        Catch ae As AggregateException

            For Each inner In ae.InnerExceptions
                Console.WriteLine(inner.Message)
            Next
        End Try

        Console.WriteLine("Press any key to exit.")
        Console.ReadKey()
    End Sub
End Class

Class WorkerWithTimer

    Dim internalTokenSource As CancellationTokenSource
    Dim token As CancellationToken
    Dim myTimer As Timer

    Public Sub WorkerWithTimer()

        internalTokenSource = New CancellationTokenSource()
        token = internalTokenSource.Token

        ' A toy cancellation trigger that times out after 3 seconds
        ' if the user does not press 'c'.
        myTimer = New Timer(New TimerCallback(AddressOf CancelAfterTimeout), Nothing, 3000, 3000)
    End Sub


    Public Sub DoWork(ByVal externalToken As CancellationToken)

        ' Create a new token that combines the internal and external tokens.
        Dim internalToken As CancellationToken = internalTokenSource.Token
        Dim linkedCts As CancellationTokenSource =
        CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken)
        Using (linkedCts)
            Try
                DoWorkInternal(linkedCts.Token)

            Catch e As OperationCanceledException
                If e.CancellationToken = internalToken Then
                    Console.WriteLine("Operation timed out.")

                ElseIf e.CancellationToken = externalToken Then
                    Console.WriteLine("Canceled by external token.")
                    externalToken.ThrowIfCancellationRequested()
                End If

            End Try
        End Using
    End Sub


    Private Sub DoWorkInternal(ByVal token As CancellationToken)

        For i As Integer = 0 To 1000

            If token.IsCancellationRequested Then

                ' We need to dispose the timer if cancellation
                ' was requested by the external token.
                myTimer.Dispose()

                ' Output for demonstration purposes.
                Console.WriteLine("\r\nCancelling per request.")

                ' Throw the exception.
                token.ThrowIfCancellationRequested()
            End If

            ' Simulating work.
            Thread.SpinWait(7500000)
            Console.Write("working... ")
        Next
    End Sub

    Public Sub CancelAfterTimeout(ByVal state As Object)

        Console.WriteLine("\r\nTimer fired.")
        internalTokenSource.Cancel()
        myTimer.Dispose()
    End Sub
End Class
namespace WaitForMultiple
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class LinkedTokenSourceDemo
    {
        static void Main()
        {
            WorkerWithTimer worker = new WorkerWithTimer();
            CancellationTokenSource cts = new CancellationTokenSource();

            // Task for UI thread, so we can call Task.Wait wait on the main thread.
            Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.");
                Console.WriteLine("Or let the task time out by doing nothing.");
                if (Console.ReadKey().KeyChar == 'c')
                    cts.Cancel();
            });

            // Let the user read the UI message.
            Thread.Sleep(1000);

            // Start the worker task.
            Task task = Task.Factory.StartNew(() => worker.DoWork(cts.Token), cts.Token);


            try
            {
                task.Wait(cts.Token);
            }

            catch (OperationCanceledException e)
            {
                if (e.CancellationToken == cts.Token)
                    Console.WriteLine("Canceled from UI thread throwing OCE.");
            }


            catch (AggregateException ae)
            {
                Console.WriteLine("AggregateException caught: " + ae.InnerException);
                foreach (var inner in ae.InnerExceptions)
                {
                    Console.WriteLine(inner.Message + inner.Source);
                }
            }

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }

    class WorkerWithTimer
    {
        CancellationTokenSource internalTokenSource = new CancellationTokenSource();
        CancellationToken internalToken;
        CancellationToken externalToken;
        Timer timer;

        public WorkerWithTimer()
        {
            internalTokenSource = new CancellationTokenSource();
            internalToken = internalTokenSource.Token;

            // A toy cancellation trigger that times out after 3 seconds
            // if the user does not press 'c'.
            timer = new Timer(new TimerCallback(CancelAfterTimeout), null, 3000, 3000);
        }


        public void DoWork(CancellationToken externalToken)
        {
            // Create a new token that combines the internal and external tokens.
            this.internalToken = internalTokenSource.Token;
            this.externalToken = externalToken;

            using (CancellationTokenSource linkedCts =
                    CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
            {
                try
                {
                    DoWorkInternal(linkedCts.Token);
                }
                catch (OperationCanceledException)
                {
                    if (internalToken.IsCancellationRequested)
                    {
                        Console.WriteLine("Operation timed out.");
                    }
                    else if (externalToken.IsCancellationRequested)
                    {
                        Console.WriteLine("Cancelling per user request.");
                        externalToken.ThrowIfCancellationRequested();
                    }
                }
            }
        }


        private void DoWorkInternal(CancellationToken token)
        {
            for (int i = 0; i < 1000; i++)
            {
                if (token.IsCancellationRequested)
                {
                    // We need to dispose the timer if cancellation
                    // was requested by the external token.
                    timer.Dispose();

                    // Throw the exception.
                    token.ThrowIfCancellationRequested();
                }

                 // Simulating work.
                Thread.SpinWait(7500000);
                Console.Write("working... ");
            }
        }

        public void CancelAfterTimeout(object state)
        {
            Console.WriteLine("\r\nTimer fired.");
            internalTokenSource.Cancel();
            timer.Dispose();
        }
    }
}

연결된 토큰이 OperationCanceledException을 throw하면 선행 작업 토큰이 아닌 연결된 토큰이 예외에 전달됩니다. 취소된 토큰을 확인하려면 선행 작업 토큰의 상태를 직접 확인합니다.

이 예제에서 AggregateException은 전혀 throw되지 않지만 여기서는 이 예외를 catch합니다. 실제 상황에서는 OperationCanceledException 외에 작업 대리자에서 throw되는 다른 모든 예외가 OperationCanceledException에 래핑되기 때문입니다.

참고 항목

개념

취소