방법: 폴링을 통해 취소 요청 수신 대기
다음 예제에서는 호출 스레드에서 취소가 요청되었는지 여부를 확인하기 위해 사용자 코드를 사용하여 취소 토큰을 일정한 간격으로 폴링할 수 있는 한 가지 방법을 보여 줍니다. 이 예제에서는 System.Threading.Tasks.Task 형식을 사용하지만 동일한 패턴이 System.Threading.ThreadPool 형식 또는 System.Threading.Thread 형식을 통해 직접 만든 비동기 작업에도 적용됩니다.
예제
폴링에는 부울 IsCancellationRequested 속성의 값을 주기적으로 읽을 수 있는 특정 종류의 루프 또는 재귀 코드가 필요합니다. System.Threading.Tasks.Task 형식을 사용하고 있고 호출 스레드에서 작업이 완료되기를 기다리는 경우 ThrowIfCancellationRequested 메서드를 사용하여 속성을 확인하고 예외를 throw할 수 있습니다. 이 메서드를 사용하면 요청에 대한 응답으로 올바른 예외가 throw되도록 할 수 있습니다. Task를 사용하는 경우 수동으로 OperationCanceledException을 throw하는 것보다 이 메서드를 호출하는 것이 좋습니다. 예외를 throw하지 않아도 되는 경우에는 속성을 확인하고 속성이 true이면 이 메서드에서 반환할 수 있습니다.
Class CancelByPolling
Shared Sub Main()
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.Factory.StartNew(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.Factory.StartNew(Sub() PollByTimeSpan(tokenSource.Token), tokenSource.Token)
Console.WriteLine("Press 'c' to cancel")
If Console.ReadKey().KeyChar = "c"c Then
tokenSource.Cancel()
Console.WriteLine("Press any key to exit.")
End If
Console.ReadKey()
End Sub
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
End Class
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.Factory.StartNew(() => 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.Factory.StartNew(() => PollByTimeSpan(tokenSource.Token), tokenSource.Token);
Console.WriteLine("Press 'c' to cancel");
if (Console.ReadKey().KeyChar == 'c')
{
tokenSource.Cancel();
Console.WriteLine("Press any key to exit.");
}
Console.ReadKey();
}
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();
}
}
}
}
ThrowIfCancellationRequested 호출은 매우 빠르게 수행되기 때문에 루프의 오버헤드가 크게 증가하지 않습니다.
ThrowIfCancellationRequested를 호출하는 경우 취소에 대한 응답으로 예외 throw 이외의 다른 작업을 수행해야 하면 IsCancellationRequested 속성을 명시적으로 확인하기만 하면 됩니다. 이 예제에서는 실제로 코드에서 명시적 액세스와 ThrowIfCancellationRequested를 통해 속성에 두 번 액세스하는 것을 확인할 수 있습니다. 그러나 IsCancellationRequested 속성을 읽는 동작에는 액세스당 하나의 volatile 읽기 명령만 사용되므로 이러한 이중 액세스는 성능 측면에서 별다른 도움이 되지 않습니다. 수동으로 OperationCanceledException을 throw하는 것보다 이 메서드를 호출하는 것이 좋습니다.