방법: 폴링을 통해 취소 요청 수신 대기
다음 예제는 사용자 코드가 정기적으로 취소 토큰을 폴링하여 호출 스레드에서 취소가 요청되었는지 여부를 확인하는 방법을 보여줍니다. 이 예제에서는 System.Threading.Tasks.Task 유형을 사용하지만, 동일한 패턴이 System.Threading.ThreadPool 유형 또는 System.Threading.Thread 유형으로 직접 생성된 비동기 작업에 적용됩니다.
예시
폴링에는 부울 IsCancellationRequested 속성의 값을 정기적으로 읽을 수 있는 특정한 종류의 루프 또는 순환 코드가 필요합니다. System.Threading.Tasks.Task 유형을 사용 중이고 작업이 호출 스레드에서 완료될 때까지 대기 중인 경우 ThrowIfCancellationRequested 메서드를 사용하여 속성을 확인하고 예외를 throw할 수 있습니다. 이 메서드를 사용하여 요청에 대한 응답으로 올바른 예외가 throw되도록 합니다. Task를 사용하는 경우 이 메서드를 호출하는 것이 OperationCanceledException를 수동으로 throw하는 것보다 좋습니다. 예외를 throw할 필요가 없는 경우 속성을 확인하고 속성이 true
인 경우 메서드에서 반환할 수 있습니다.
using System;
using System.Threading;
using System.Threading.Tasks;
public struct Rectangle
{
public int columns;
public int rows;
}
class CancelByPolling
{
static void Main()
{
var tokenSource = new CancellationTokenSource();
// Toy object for demo purposes
Rectangle rect = new Rectangle() { columns = 1000, rows = 500 };
// Simple cancellation scenario #1. Calling thread does not wait
// on the task to complete, and the user delegate simply returns
// on cancellation request without throwing.
Task.Run(() => NestedLoops(rect, tokenSource.Token), tokenSource.Token);
// Simple cancellation scenario #2. Calling thread does not wait
// on the task to complete, and the user delegate throws
// OperationCanceledException to shut down task and transition its state.
// Task.Run(() => PollByTimeSpan(tokenSource.Token), tokenSource.Token);
Console.WriteLine("Press 'c' to cancel");
if (Console.ReadKey(true).KeyChar == 'c') {
tokenSource.Cancel();
Console.WriteLine("Press any key to exit.");
}
Console.ReadKey();
tokenSource.Dispose();
}
static void NestedLoops(Rectangle rect, CancellationToken token)
{
for (int col = 0; col < rect.columns && !token.IsCancellationRequested; col++) {
// Assume that we know that the inner loop is very fast.
// Therefore, polling once per column in the outer loop condition
// is sufficient.
for (int row = 0; row < rect.rows; row++) {
// Simulating work.
Thread.SpinWait(5_000);
Console.Write("{0},{1} ", col, row);
}
}
if (token.IsCancellationRequested) {
// Cleanup or undo here if necessary...
Console.WriteLine("\r\nOperation canceled");
Console.WriteLine("Press any key to exit.");
// If using Task:
// token.ThrowIfCancellationRequested();
}
}
}
Imports System.Threading
Imports System.Threading.Tasks
Public Structure Rectangle
Public columns As Integer
Public rows As Integer
End Structure
Class CancelByPolling
Shared Sub Main11()
Dim tokenSource As New CancellationTokenSource()
' Toy object for demo purposes
Dim rect As New Rectangle()
rect.columns = 1000
rect.rows = 500
' Simple cancellation scenario #1. Calling thread does not wait
' on the task to complete, and the user delegate simply returns
' on cancellation request without throwing.
Task.Run(Sub() NestedLoops(rect, tokenSource.Token), tokenSource.Token)
' Simple cancellation scenario #2. Calling thread does not wait
' on the task to complete, and the user delegate throws
' OperationCanceledException to shut down task and transition its state.
' Task.Run(Sub() PollByTimeSpan(tokenSource.Token), tokenSource.Token)
Console.WriteLine("Press 'c' to cancel")
If Console.ReadKey(True).KeyChar = "c"c Then
tokenSource.Cancel()
Console.WriteLine("Press any key to exit.")
End If
Console.ReadKey()
tokenSource.Dispose()
End Sub
Shared Sub NestedLoops(ByVal rect As Rectangle, ByVal token As CancellationToken)
Dim col As Integer
For col = 0 To rect.columns - 1
' Assume that we know that the inner loop is very fast.
' Therefore, polling once per column in the outer loop condition
' is sufficient.
For row As Integer = 0 To rect.rows - 1
' Simulating work.
Thread.SpinWait(5000)
Console.Write("0',1' ", col, row)
Next
Next
If token.IsCancellationRequested = True Then
' Cleanup or undo here if necessary...
Console.WriteLine(vbCrLf + "Operation canceled")
Console.WriteLine("Press any key to exit.")
' If using Task:
' token.ThrowIfCancellationRequested()
End If
End Sub
End Class
ThrowIfCancellationRequested 호출은 매우 빠르며 루프에서 오버헤드를 그다지 유발하지 않습니다.
ThrowIfCancellationRequested를 호출하는 경우 예외를 throw하는 것 외에 취소에 대한 응답으로 수행해야 할 다른 작업이 있는 경우에만 IsCancellationRequested 속성을 명시적으로 확인해야 합니다. 이 예제에서는 코드가 실제로 속성에 두 번 액세스하는 것을 볼 수 있습니다. 명시적으로 한 번 액세스하고 ThrowIfCancellationRequested 메서드에서 다시 액세스할 수 있습니다. 그러나 IsCancellationRequested 속성을 읽는 작업에는 액세스당 하나의 volatile 읽기 명령만 포함되므로 성능상의 관점에서는 두 번의 액세스가 중요하지 않습니다. OperationCanceledException을 수동으로 throw하는 것보다 메서드를 호출하는 것이 여전히 더 좋습니다.
참고 항목
.NET