如何:透過輪詢接聽取消要求
下列範例將示範一種方式:使用者程式碼可定期輪詢取消語彙基元,以查看呼叫執行緒是否已要求取消。 這個範例會使用 System.Threading.Tasks.Task 類型,但相同的模式適用於 System.Threading.ThreadPool 類型或 System.Threading.Thread 類型直接建立的非同步作業。
範例
輪詢需要某種可定期讀取布林值 IsCancellationRequested 屬性值的迴圈或遞迴程式碼。 如果您使用 System.Threading.Tasks.Task 類型且正在等候呼叫執行緒上的工作完成,您可以使用 ThrowIfCancellationRequested 方法來檢查屬性,並擲回例外狀況。 使用此方法,您可以確保會擲回正確的例外狀況來回應要求。 如果您使用 Task,則呼叫此方法優於手動擲回 OperationCanceledException。 如果您不需擲回例外狀況,則只需檢查屬性,如果屬性為 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,當您要進行其他工作來回應取消 (擲回例外狀況除外) 時,只需明確檢查 IsCancellationRequested 屬性。 在此範例中,您可以看到程式碼會實際存取屬性兩次:一次是明確的存取,另一次則是在 ThrowIfCancellationRequested 方法中。 但由於讀取 IsCancellationRequested 屬性的動作牽涉到每次存取只能有一個暫時性讀取指令,因此,從效能觀點來看,兩次存取並不重要。 最好還是呼叫方法,而非手動擲回 OperationCanceledException。