다음을 통해 공유


취소

.NET Framework 버전 4에는 비동기 또는 장기 실행 동기 작업의 협조적 취소를 위한 통합 모델이 새로 도입되었습니다. 이 모델은 취소 토큰이라고 하는 간단한 개체를 기반으로 합니다. 새 스레드 또는 작업을 만드는 등의 방법으로 취소할 수 있는 작업을 호출하는 개체는 해당 작업에 취소 토큰을 전달합니다. 그러면 토큰을 받은 작업은 다시 이 토큰의 복사본을 다른 작업에 전달할 수 있습니다. 토큰을 만든 개체는 나중에 이 토큰을 사용하여 실행 중인 작업의 중지를 요청할 수 있습니다. 취소 요청은 요청 개체만 실행할 수 있으며 각 수신기는 이러한 요청을 인식하고 적시에 이에 응답해야 합니다. 다음 그림에서는 토큰 소스와 이 토큰의 모든 복사본 간 관계를 보여 줍니다.

CancellationTokenSource 및 CancellationToken

새 취소 모델을 사용하면 취소 인식 응용 프로그램과 라이브러리를 보다 쉽게 만들고 다음과 같은 기능을 지원할 수 있습니다.

  • 취소가 협조적으로 수행되며 수신기에 강제로 적용되지 않습니다. 수신기는 취소 요청에 대한 응답으로 작업을 적절하게 종료하는 방법을 결정합니다.

  • 요청이 수신과 다릅니다. 취소할 수 있는 작업을 호출하는 개체는 취소가 요청되는 시점(있는 경우)을 제어할 수 있습니다.

  • 요청 개체가 하나의 메서드 호출만 사용하여 토큰의 모든 복사본에 대한 취소 요청을 실행합니다.

  • 수신기에서 여러 토큰을 하나의 연결된 토큰으로 조인하여 동시에 수신할 수 있습니다.

  • 사용자 코드는 라이브러리 코드의 취소 요청을 인식하고 이에 응답할 수 있으며 라이브러리 코드는 사용자 코드의 취소 요청을 인식하고 이에 응답할 수 있습니다.

  • 수신기가 폴링, 콜백 등록 또는 대기 핸들 대기를 통해 취소 요청 알림을 받을 수 있습니다.

새 취소 형식

새 취소 프레임워크는 다음 표에 나와 있는 관련 형식의 집합으로 구현됩니다.

형식 이름

설명

CancellationTokenSource

취소 토큰을 만들 뿐 아니라 이 토큰의 모든 복사본에 대한 취소 요청을 실행하는 개체입니다.

CancellationToken

하나 이상의 수신기에 대개 메서드 매개 변수로 전달되는 간단한 값 형식입니다. 수신기는 폴링, 콜백 또는 대기 핸들을 통해 토큰의 IsCancellationRequested 속성 값을 모니터링합니다.

OperationCanceledException

이 예외의 새 오버로드는 취소 토큰을 입력 매개 변수로 받아들입니다. 수신기는 취소의 소스를 확인하고 다른 수신기에 취소 요청에 응답했음을 알리기 위해 선택적으로 이 예외를 throw할 수 있습니다.

새 취소 모델은 여러 가지 형식으로 .NET Framework에 통합되었습니다. 가장 중요한 형식은 System.Threading.Tasks.Parallel, System.Threading.Tasks.Task, System.Threading.Tasks.Task<TResult>System.Linq.ParallelEnumerable입니다. 모든 새 라이브러리 및 응용 프로그램 코드에 이 새 취소 모델을 사용하는 것이 좋습니다.

코드 예제

다음 예제에서는 요청 개체가 CancellationTokenSource 개체를 만든 다음 취소할 수 있는 작업에 이 개체의 Token 속성을 전달합니다. 요청을 받는 작업은 폴링을 통해 토큰의 IsCancellationRequested 속성 값을 모니터링합니다. 값이 true이면 수신기는 적절한 방식으로 작업을 종료할 수 있습니다. 이 예제에서는 단순히 메서드를 종료합니다. 대부분의 경우 이와 같이 메서드를 종료하기만 하면 됩니다.

참고참고

이 예제에서는 QueueUserWorkItem 메서드를 사용하여 새 취소 프레임워크가 레거시 API와 호환됨을 보여 줍니다.새 기본 System.Threading.Tasks.Task 형식을 사용하는 예제는 방법: 작업 및 해당 자식 취소를 참조하십시오.

Shared Sub CancelWithThreadPoolMiniSnippet()


    'Thread 1: The Requestor
    ' Create the token source.
    Dim cts As New CancellationTokenSource()

    ' Pass the token to the cancelable operation.
    ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoSomeWork), cts.Token)

    ' Request cancellation by setting a flag on the token.
    cts.Cancel()
    ' end block
End Sub

'Thread 2: The Listener
Shared Sub DoSomeWork(ByVal obj As Object)

    Dim token As CancellationToken = CType(obj, CancellationToken)
    For i As Integer = 0 To 1000000

        ' Simulating work.
        Thread.SpinWait(5000000)

        If token.IsCancellationRequested Then

            ' Perform cleanup if necessary.
            '...
            ' Terminate the operation.
            Exit For
        End If
    Next
End Sub
static void CancelWithThreadPoolMiniSnippet()
{

    //Thread 1: The Requestor
    // Create the token source.
    CancellationTokenSource cts = new CancellationTokenSource();

    // Pass the token to the cancelable operation.
    ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);

    // Request cancellation by setting a flag on the token.
    cts.Cancel();
}

//Thread 2: The Listener
static void DoSomeWork(object obj)
{
    CancellationToken token = (CancellationToken)obj;
    for (int i = 0; i < 100000; i++)
    {
        // Simulating work.
        Thread.SpinWait(5000000);

        if (token.IsCancellationRequested)
        {
            // Perform cleanup if necessary.
            //...
            // Terminate the operation.
            break;
        }
    }
}

작업 취소 및 개체 취소

새로운 취소 프레임워크에서 취소는 개체가 아니라 작업에 적용됩니다. 취소 요청은 필요한 모든 정리가 수행된 후 가능한 한 빨리 작업이 중지되어야 함을 의미합니다. 하나의 취소 토큰은 하나의 "취소할 수 있는 작업"에 해당하지만, 프로그램에서 이 작업이 구현될 수 있습니다. 토큰의 IsCancellationRequested 속성을 true로 설정한 후에는 false로 다시 설정할 수 없습니다. 따라서 취소 토큰은 취소된 후 다시 사용할 수 없습니다.

개체 취소 메커니즘이 필요하면 다음 예제와 같이 작업 취소 메커니즘을 기반으로 할 수 있습니다.

Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token

' User defined Class with its own method for cancellation
Dim obj1 As New MyCancelableObject()
Dim obj2 As New MyCancelableObject()
Dim obj3 As New MyCancelableObject()

' Register the object's cancel method with the token's
' cancellation request.
token.Register(Sub() obj1.Cancel())
token.Register(Sub() obj2.Cancel())
token.Register(Sub() obj3.Cancel())

' Request cancellation on the token.
cts.Cancel()
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

// User defined Class with its own method for cancellation
var obj1 = new MyCancelableObject();
var obj2 = new MyCancelableObject();
var obj3 = new MyCancelableObject();

// Register the object's cancel method with the token's
// cancellation request.
token.Register(() => obj1.Cancel());
token.Register(() => obj2.Cancel());
token.Register(() => obj3.Cancel());

// Request cancellation on the token.
cts.Cancel();

개체가 둘 이상의 취소할 수 있는 작업을 지원하면 각 작업에 개별 토큰을 입력으로 전달합니다. 이렇게 하면 다른 작업에 영향을 주지 않고 특정 작업을 취소할 수 있습니다.

취소 요청 수신 및 응답

취소할 수 있는 작업의 구현자는 사용자 대리자에서 취소 요청의 응답으로 작업을 종료하는 방법을 결정합니다. 대부분의 경우 사용자 대리자는 필요한 모든 정리를 수행한 후 즉시 반환할 수 있습니다.

그러나 보다 복잡한 경우에는 취소가 발생했음을 사용자 대리자가 라이브러리 코드에 알려야 할 수 있습니다. 이러한 경우 작업을 종료하는 올바른 방법은 대리자가 ThrowIfCancellationRequested()를 호출하여 OperationCanceledException이 throw되도록 하는 것입니다. .NET Framework 버전 4에서는 이 예외의 새 오버로드가 CancellationToken을 인수로 받아들입니다. 라이브러리 코드는 사용자 대리자 스레드에서 이 예외를 catch하고 이 예외의 토큰을 검사하여 이 예외가 협조적 취소를 나타내는지 아니면 다른 예외 상황을 나타내는지 여부를 확인할 수 있습니다.

Task 클래스는 이런 식으로 OperationCanceledException을 처리합니다. 자세한 내용은 작업 취소를 참조하십시오.

폴링으로 수신

반복되는 장기 실행 계산의 경우 CancellationToken.IsCancellationRequested 속성의 값을 주기적으로 폴링하여 취소 요청을 수신 대기할 수 있습니다. 값이 true이면 메서드가 정리를 수행한 후 가능한 한 빨리 종료해야 합니다. 최적의 폴링 빈도는 응용 프로그램의 종료에 따라 달라집니다. 지정된 프로그램에 대한 최적의 폴링 빈도는 개발자가 결정합니다. 폴링 자체는 성능에 큰 영향을 주지 않습니다. 다음 예제에서는 폴링할 수 있는 한 가지 방법을 보여 줍니다.

    Shared Sub NestedLoops(ByVal rect As Rectangle, ByVal token As CancellationToken)

        For x As Integer = 0 To rect.columns

            For y As Integer = 0 To rect.rows

                ' Simulating work.
                Thread.SpinWait(5000)
                Console.Write("0' end block,1' end block ", x, y)
            Next

            ' Assume that we know that the inner loop is very fast.
            ' Therefore, checking once per row is sufficient.
            If token.IsCancellationRequested = True Then

                ' Cleanup or undo here if necessary...
                Console.WriteLine("\r\nCancelling after row 0' end block.", x)
                Console.WriteLine("Press any key to exit.")
                ' then...
                Exit For
                ' ...or, if using Task:
                ' token.ThrowIfCancellationRequested()
            End If
        Next
    End Sub

static void NestedLoops(Rectangle rect, CancellationToken token)
{
    for (int x = 0; x < rect.columns && !token.IsCancellationRequested; x++)
    {
        for (int y = 0; y < rect.rows; y++)
        {
            // Simulating work.
            Thread.SpinWait(5000);
            Console.Write("{0},{1} ", x, y);
        }

        // Assume that we know that the inner loop is very fast.
        // Therefore, checking once per row is sufficient.
        if (token.IsCancellationRequested)
        {
            // Cleanup or undo here if necessary...
            Console.WriteLine("\r\nCancelling after row {0}.", x);
            Console.WriteLine("Press any key to exit.");
            // then...
            break;
            // ...or, if using Task:
            // token.ThrowIfCancellationRequested();
        }
    }
}

자세한 예제는 방법: 폴링을 통해 취소 요청 수신 대기를 참조하십시오.

콜백을 등록하여 수신

일부 작업은 취소 토큰의 값을 적시에 확인할 수 없는 방식으로 차단될 수 있습니다. 이러한 경우 취소 요청을 받을 때 메서의 차단을 해제하는 콜백 메서드를 등록할 수 있습니다.

Register 메서드는 특별히 이러한 용도로 사용되는 CancellationTokenRegistration 개체를 반환합니다. 다음 예제에서는 Register 메서드를 사용하여 비동기 웹 요청을 취소하는 방법을 보여 줍니다.

Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
Dim wc As New WebClient()

' To request cancellation on the token
' will call CancelAsync on the WebClient.
token.Register(Sub() wc.CancelAsync())

Console.WriteLine("Starting request")
wc.DownloadStringAsync(New Uri("https://www.contoso.com"))
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;            
WebClient wc = new WebClient();

// To request cancellation on the token
// will call CancelAsync on the WebClient.
token.Register(() => wc.CancelAsync());

Console.WriteLine("Starting request");
wc.DownloadStringAsync(new Uri("https://www.contoso.com"));

CancellationTokenRegistration 개체는 스레드 동기화를 관리하고 정확한 시점에 콜백이 실행을 중지하도록 합니다.

교착 상태가 발생하지 않고 시스템이 제대로 응답하게 하려면 콜백을 등록할 때 다음 지침을 따라야 합니다.

  • 콜백 메서드는 동기적으로 호출되므로 콜백이 반환될 때까지 Cancel에 대한 호출이 반환되지 않습니다. 따라서 콜백 메서드는 속도가 빨라야 합니다.

  • 콜백이 실행되는 동안 Dispose를 호출하면 콜백이 기다리고 있는 잠금을 보내지 않게 되므로 프로그램이 교착 상태에 빠질 수 있습니다. Dispose가 반환되고 나면 콜백에 필요한 리소스를 모두 해제할 수 있습니다.

  • 콜백에서 수동 스레드를 수행하거나 콜백에 SynchronizationContext를 사용하지 말아야 합니다. 특정 스레드에 대해 콜백을 실행해야 하는 경우에는 System.Threading.CancellationTokenRegistration 생성자를 사용하여 대상 syncContext가 활성 SynchronizationContext.Current임을 지정할 수 있도록 해야 합니다. 콜백에서 수동 스레드를 수행하면 교착 상태가 발생할 수 있습니다.

자세한 예제는 방법: 취소 요청에 대한 콜백 등록를 참조하십시오.

대기 핸들을 사용하여 수신

취소할 수 있는 작업이 System.Threading.ManualResetEvent 또는 System.Threading.Semaphore 같은 동기화 기본 형식을 기다리는 동안 차단될 수 있는 경우 CancellationToken.WaitHandle 속성을 사용하여 작업이 이벤트와 취소 요청을 둘 다 기다리도록 설정할 수 있습니다. 취소 토큰의 대기 핸들은 취소 요청에 대한 응답으로 신호를 받으면 메서드는 WaitAny 메서드의 반환 값을 사용하여 이 취소 토큰이 신호를 받은 취소 토큰인지 여부를 확인할 수 있습니다. 그러면 작업이 필요에 따라 종료되거나 OperationCanceledException을 throw할 수 있습니다.

' Wait on the event if it is not signaled.
Dim myWaitHandle(2) As WaitHandle
myWaitHandle(0) = mre
myWaitHandle(1) = token.WaitHandle
Dim eventThatSignaledIndex =
    WaitHandle.WaitAny(myWaitHandle, _
                        New TimeSpan(0, 0, 20))
// Wait on the event if it is not signaled.
int eventThatSignaledIndex =
    WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
                        new TimeSpan(0, 0, 20));

.NET Framework 버전 4, System.Threading.ManualResetEventSlimSystem.Threading.SemaphoreSlim을 대상으로 하는 새 코드에서는 둘 다 해당 Wait 메서드의 새 취소 프레임워크를 지원합니다. 이 메서드에 CancellationToken을 전달하여 취소가 요청될 때 이벤트가 깨어나서 OperationCanceledException을 throw하도록 할 수 있습니다.

Try

' mres is a ManualResetEventSlim
  mres.Wait(token)
Catch e As OperationCanceledException

    ' Throw immediately to be responsive. The
    ' alternative is to do one more item of work,
    ' and throw on next iteration, because 
    ' IsCancellationRequested will be true.
    Console.WriteLine("Canceled while waiting.")
    Throw
End Try

 ' Simulating work.
Console.Write("Working...")
Thread.SpinWait(500000)
try
{
    // mres is a ManualResetEventSlim
    mres.Wait(token);
}
catch (OperationCanceledException)
{
    // Throw immediately to be responsive. The
    // alternative is to do one more item of work,
    // and throw on next iteration, because 
    // IsCancellationRequested will be true.
    Console.WriteLine("The wait operation was canceled.");
    throw;
}

Console.Write("Working...");
// Simulating work.
Thread.SpinWait(500000);

자세한 예제는 방법: 대기 핸들이 있는 취소 요청 수신 대기를 참조하십시오.

동시에 여러 토큰 수신

경우에 따라 수신기에서 여러 취소 토큰을 동시에 수신할 수 있습니다. 예를 들어, 취소할 수 있는 작업은 외부에서 메서드 매개 변수의 인수로 전달되는 토큰 외에도 내부 취소 토큰을 모니터링해야 할 수 있습니다. 이를 위해서는 다음 예제와 같이 둘 이상의 토큰을 하나의 토큰으로 조인할 수 있는 연결된 소스 토큰을 만듭니다.

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
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();
            }
        }
    }
}

연결된 토큰 소스를 사용하여 조인 작업을 완료한 후에는 해당 연결된 토큰 소스에 대해 Dispose를 호출해야 합니다. 자세한 예제는 방법: 여러 개의 취소 요청 수신 대기를 참조하십시오.

라이브러리 코드와 사용자 코드 간의 협조

통합 취소 프레임워크를 사용하면 협조적 방식으로 라이브러리 코드로 사용자 코드를 취소하고 사용자 코드로 라이브러리 코드를 취소할 수 있습니다. 원활한 협조가 이루어지려면 두 코드가 다음과 같은 지침을 따라야 합니다.

  • 라이브러리 코드가 취소할 수 있는 작업을 제공하는 경우 라이브러리 코드는 사용자 코드도 취소를 요청할 수 있도록 외부 취소 토큰을 받아들이는 공용 메서드도 제공해야 합니다.

  • 라이브러리 코드가 사용자 코드를 호출하는 경우 라이브러리 코드는 OperationCanceledException(externalToken)을 협조적 취소로 해석해야 하며, 실패 예외로 해석할 필요는 없습니다.

  • 사용자 대리자는 라이브러리 코드의 취소 요청에 적시에 응답하려고 시도해야 합니다.

System.Threading.Tasks.TaskSystem.Linq.ParallelEnumerable은 이러한 지침을 따르는 클래스의 예제입니다. 자세한 내용은 작업 취소방법: PLINQ 쿼리 취소를 참조하십시오.

참고 항목

기타 리소스

관리되는 스레딩 기본 사항