Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Dimulai dengan .NET Framework 4, .NET menggunakan model terpadu untuk pembatalan kooperatif operasi asinkron atau sinkron yang berdurasi lama. Model ini didasarkan pada objek ringan yang disebut token pembatalan. Objek yang memanggil satu atau beberapa operasi yang dapat dibatalkan, misalnya dengan membuat utas atau tugas baru, meneruskan token ke setiap operasi. Operasi individual pada gilirannya dapat meneruskan salinan token ke operasi lain. Di lain waktu, objek yang membuat token dapat menggunakannya untuk meminta agar operasi menghentikan apa yang mereka lakukan. Hanya objek yang meminta yang dapat mengeluarkan permintaan pembatalan, dan setiap pendengar bertanggung jawab untuk melihat permintaan dan menanggapinya dengan cara yang tepat dan tepat waktu.
Pola umum untuk menerapkan model pembatalan koperasi adalah:
Menginisialisasi objek CancellationTokenSource, yang mengelola dan mengirim pemberitahuan pembatalan ke token pembatalan individu.
Teruskan token yang dikembalikan oleh properti CancellationTokenSource.Token ke setiap tugas atau utas yang mendengarkan pembatalan.
Sediakan mekanisme bagi setiap tugas atau utas untuk merespons pembatalan.
CancellationTokenSource.Cancel Panggil metode untuk memberikan pemberitahuan pembatalan.
Penting
Kelas CancellationTokenSource menerapkan antarmuka IDisposable. Pastikan untuk memanggil metode CancellationTokenSource.Dispose ketika Anda telah selesai menggunakan sumber dari token pembatalan untuk membebaskan sumber daya yang tidak dikelola yang dipegangnya.
Ilustrasi berikut menunjukkan hubungan antara sumber token dan semua salinan tokennya.
Model pembatalan kooperatif memudahkan untuk membuat aplikasi dan pustaka yang sadar pembatalan, dan mendukung fitur-fitur berikut:
Pembatalan bersifat kooperatif dan tidak dipaksakan pada pendengar. Pendengar menentukan cara menghentikan secara elegan untuk merespon permintaan pembatalan.
Permintaan berbeda dari mendengarkan. Objek yang memanggil operasi yang dapat dibatalkan dapat mengontrol kapan (jika pernah) pembatalan diminta.
Objek yang meminta mengeluarkan permintaan pembatalan ke semua salinan token hanya dengan satu kali pemanggilan metode.
Pendengar dapat mendengarkan beberapa token secara bersamaan dengan menggabungkannya ke dalam satu token yang ditautkan.
Kode pengguna dapat melihat dan menanggapi permintaan pembatalan dari kode pustaka, dan kode pustaka dapat melihat dan menanggapi permintaan pembatalan dari kode pengguna.
Pendengar dapat diberi tahu tentang permintaan pembatalan dengan polling, pendaftaran panggilan balik, atau menunggu handel tunggu.
Jenis Pembatalan
Kerangka kerja pembatalan diimplementasikan sebagai sekumpulan jenis terkait, yang tercantum dalam tabel berikut.
| Nama jenis | Deskripsi |
|---|---|
| CancellationTokenSource | Objek yang membuat token pembatalan, dan juga mengeluarkan permintaan pembatalan untuk semua salinan token tersebut. |
| CancellationToken | Jenis nilai ringan diteruskan ke satu atau beberapa pendengar, biasanya sebagai parameter metode. Pendengar memantau nilai properti IsCancellationRequested dari token dengan cara polling, panggilan balik, atau handle tunggu. |
| OperationCanceledException | Kelebihan beban konstruktor pengecualian ini menerima CancellationToken sebagai parameter. Pendengar dapat secara opsional melemparkan pengecualian ini untuk memverifikasi sumber pembatalan dan memberi tahu orang lain bahwa ia telah menanggapi permintaan pembatalan. |
Model pembatalan diintegrasikan ke dalam .NET dalam berbagai jenis. Yang paling penting adalah System.Threading.Tasks.Parallel, , System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<TResult> dan System.Linq.ParallelEnumerable. Kami menyarankan agar Anda menggunakan model pembatalan kooperatif ini untuk semua pustaka dan kode aplikasi baru.
Contoh Kode
Dalam contoh berikut ini, objek peminta membuat objek CancellationTokenSource, kemudian memberikan propertinya Token ke operasi yang dapat dibatalkan. Operasi yang menerima permintaan memantau nilai IsCancellationRequested properti token dengan polling. Ketika nilai menjadi true, pendengar dapat mengakhiri dengan cara apa pun yang sesuai. Dalam contoh ini, metode hanya keluar, yang semuanya diperlukan dalam banyak kasus.
Nota
Contoh ini menggunakan metode QueueUserWorkItem untuk menunjukkan bahwa kerangka kerja pembatalan kooperatif kompatibel dengan API lama. Untuk contoh yang menggunakan jenis yang disukai System.Threading.Tasks.Task, lihat Cara: Membatalkan Tugas dan Sub-tugasnya.
using System;
using System.Threading;
public class Example
{
public static void Main()
{
// Create the token source.
CancellationTokenSource cts = new CancellationTokenSource();
// Pass the token to the cancelable operation.
ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);
Thread.Sleep(2500);
// Request cancellation.
cts.Cancel();
Console.WriteLine("Cancellation set in token source...");
Thread.Sleep(2500);
// Cancellation should have happened, so call Dispose.
cts.Dispose();
}
// Thread 2: The listener
static void DoSomeWork(object? obj)
{
if (obj is null)
return;
CancellationToken token = (CancellationToken)obj;
for (int i = 0; i < 100000; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("In iteration {0}, cancellation has been requested...",
i + 1);
// Perform cleanup if necessary.
//...
// Terminate the operation.
break;
}
// Simulate some work.
Thread.SpinWait(500000);
}
}
}
// The example displays output like the following:
// Cancellation set in token source...
// In iteration 1430, cancellation has been requested...
Imports System.Threading
Module Example1
Public Sub Main1()
' Create the token source.
Dim cts As New CancellationTokenSource()
' Pass the token to the cancelable operation.
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoSomeWork), cts.Token)
Thread.Sleep(2500)
' Request cancellation by setting a flag on the token.
cts.Cancel()
Console.WriteLine("Cancellation set in token source...")
Thread.Sleep(2500)
' Cancellation should have happened, so call Dispose.
cts.Dispose()
End Sub
' Thread 2: The listener
Sub DoSomeWork(ByVal obj As Object)
Dim token As CancellationToken = CType(obj, CancellationToken)
For i As Integer = 0 To 1000000
If token.IsCancellationRequested Then
Console.WriteLine("In iteration {0}, cancellation has been requested...",
i + 1)
' Perform cleanup if necessary.
'...
' Terminate the operation.
Exit For
End If
' Simulate some work.
Thread.SpinWait(500000)
Next
End Sub
End Module
' The example displays output like the following:
' Cancellation set in token source...
' In iteration 1430, cancellation has been requested...
Pembatalan Operasi Versus Pembatalan Objek
Dalam kerangka kerja pembatalan koperasi, pembatalan mengacu pada operasi, bukan objek. Permintaan pembatalan berarti bahwa operasi harus berhenti sesegera mungkin setelah pembersihan yang diperlukan dilakukan. Satu token pembatalan harus merujuk ke satu "operasi yang dapat dibatalkan", namun operasi tersebut dapat diterapkan dalam program Anda.
IsCancellationRequested Setelah properti token diatur ke true, properti tidak dapat diatur ulang ke false. Oleh karena itu, token pembatalan tidak dapat digunakan kembali setelah dibatalkan.
Jika Anda memerlukan mekanisme pembatalan objek, Anda dapat mendasarkannya pada mekanisme pembatalan operasi dengan memanggil CancellationToken.Register metode , seperti yang ditunjukkan dalam contoh berikut.
using System;
using System.Threading;
class CancelableObject
{
public string id;
public CancelableObject(string id)
{
this.id = id;
}
public void Cancel()
{
Console.WriteLine($"Object {id} Cancel callback");
// Perform object cancellation here.
}
}
public class Example1
{
public static void Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
// User defined Class with its own method for cancellation
var obj1 = new CancelableObject("1");
var obj2 = new CancelableObject("2");
var obj3 = new CancelableObject("3");
// 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();
// Call Dispose when we're done with the CancellationTokenSource.
cts.Dispose();
}
}
// The example displays the following output:
// Object 3 Cancel callback
// Object 2 Cancel callback
// Object 1 Cancel callback
Imports System.Threading
Class CancelableObject
Public id As String
Public Sub New(id As String)
Me.id = id
End Sub
Public Sub Cancel()
Console.WriteLine("Object {0} Cancel callback", id)
' Perform object cancellation here.
End Sub
End Class
Module ExampleOb1
Public Sub MainOb1()
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
' User defined Class with its own method for cancellation
Dim obj1 As New CancelableObject("1")
Dim obj2 As New CancelableObject("2")
Dim obj3 As New CancelableObject("3")
' 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()
' Call Dispose when we're done with the CancellationTokenSource.
cts.Dispose()
End Sub
End Module
' The example displays output like the following:
' Object 3 Cancel callback
' Object 2 Cancel callback
' Object 1 Cancel callback
Jika objek mendukung lebih dari satu operasi yang dapat dibatalkan bersamaan, berikan token terpisah sebagai input ke setiap operasi yang dapat dibatalkan yang berbeda. Dengan begitu, satu operasi dapat dibatalkan tanpa memengaruhi yang lain.
Mendengarkan dan Menanggapi Permintaan Pembatalan
Dalam delegasi pengguna, pelaksana operasi yang dapat dibatalkan menentukan cara mengakhiri operasi sebagai respons terhadap permintaan pembatalan. Dalam banyak kasus, delegasi pengguna hanya dapat melakukan pembersihan yang diperlukan dan kemudian segera kembali.
Namun, dalam kasus yang lebih kompleks, mungkin perlu bagi delegasi pengguna untuk memberi tahu kode pustaka bahwa pembatalan telah terjadi. Dalam kasus seperti itu, cara yang benar untuk mengakhiri operasi adalah agar delegasi memanggil metode ThrowIfCancellationRequested, yang akan menyebabkan OperationCanceledException dilempar. Kode pustaka dapat menangkap pengecualian ini pada thread delegasi pengguna dan memeriksa token pengecualian untuk menentukan apakah pengecualian menunjukkan pembatalan kooperatif atau beberapa situasi luar biasa lainnya.
Kelas Task menangani OperationCanceledException dengan cara ini. Untuk informasi selengkapnya, lihat Pembatalan Tugas.
Mendengarkan melalui Polling
Untuk komputasi jangka panjang yang melakukan pengulangan atau rekursif, Anda dapat memantau permintaan pembatalan dengan secara berkala memeriksa nilai properti CancellationToken.IsCancellationRequested. Jika nilainya adalah true, metode harus membersihkan dan mengakhiri secepat mungkin. Frekuensi optimal polling tergantung pada jenis aplikasi. Terserah pengembang untuk menentukan frekuensi polling terbaik untuk program tertentu. Polling itu sendiri tidak berdampak signifikan pada performa. Contoh berikut menunjukkan salah satu cara yang mungkin untuk melakukan polling.
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();
}
}
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
Untuk contoh yang lebih lengkap, lihat Cara: Memantau Permintaan Pembatalan dengan Polling.
Mendengarkan dengan Mendaftarkan Panggilan Balik
Beberapa operasi dapat diblokir sedimikian rupa sehingga mereka tidak dapat memeriksa nilai token pembatalan secara tepat waktu. Untuk kasus ini, Anda dapat mendaftarkan metode panggilan balik yang membuka blokir metode saat permintaan pembatalan diterima.
Metode Register akan mengembalikan sebuah objek CancellationTokenRegistration yang digunakan khusus untuk tujuan ini. Contoh berikut menunjukkan cara menggunakan Register metode untuk membatalkan permintaan web asinkron.
using System;
using System.Net.Http;
using System.Threading;
class Example4
{
static void Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
StartWebRequest(cts.Token);
// Cancellation will cause the web
// request to be cancelled.
cts.Cancel();
}
static void StartWebRequest(CancellationToken token)
{
var client = new HttpClient();
token.Register(() =>
{
client.CancelPendingRequests();
Console.WriteLine("Request cancelled!");
});
Console.WriteLine("Starting request.");
client.GetStringAsync(new Uri("http://www.contoso.com"));
}
}
Imports System.Net
Imports System.Net.Http
Imports System.Threading
Class Example4
Private Shared Sub Main4()
Dim cts As New CancellationTokenSource()
StartWebRequest(cts.Token)
' cancellation will cause the web
' request to be cancelled
cts.Cancel()
End Sub
Private Shared Sub StartWebRequest(token As CancellationToken)
Dim client As New HttpClient()
token.Register(Sub()
client.CancelPendingRequests()
Console.WriteLine("Request cancelled!")
End Sub)
Console.WriteLine("Starting request.")
client.GetStringAsync(New Uri("http://www.contoso.com"))
End Sub
End Class
Objek CancellationTokenRegistration mengelola sinkronisasi utas dan memastikan bahwa panggilan balik akan berhenti dijalankan pada titik waktu yang tepat.
Untuk memastikan respons sistem dan untuk menghindari kebuntuan, pedoman berikut harus diikuti saat mendaftarkan panggilan balik:
Metode panggilan balik harus cepat karena dipanggil secara sinkron dan oleh karena itu panggilan ke Cancel tidak kembali sampai panggilan balik kembali.
Jika Anda memanggil Dispose saat panggilan balik berjalan, dan Anda menahan kunci yang sedang ditunggu oleh panggilan balik, program Anda dapat mengalami kebuntuan. Setelah
Disposekembali, Anda dapat membebaskan sumber daya apa pun yang diperlukan oleh panggilan balik.Panggilan balik tidak boleh melakukan penggunaan utas manual atau SynchronizationContext dalam panggilan balik. Jika callback harus berjalan pada utas tertentu, gunakan System.Threading.CancellationTokenRegistration konstruktor yang memungkinkan Anda menentukan bahwa syncContext target yang aktif adalah SynchronizationContext.Current. Menggunakan utas manual dalam panggilan balik dapat menyebabkan deadlock.
Untuk contoh yang lebih lengkap, lihat Cara: Mendaftarkan Panggilan Balik untuk Permintaan Pembatalan.
Mendengarkan dengan Menggunakan Handel Tunggu
Ketika operasi yang dapat dibatalkan dapat terblokir saat menunggu pada primitive sinkronisasi seperti System.Threading.ManualResetEvent atau System.Threading.Semaphore, Anda dapat menggunakan properti CancellationToken.WaitHandle untuk memungkinkan operasi menunggu sinyal dan permintaan pembatalan. Pengendali tunggu dari token pembatalan akan diaktifkan sebagai respons terhadap permintaan pembatalan, dan metode dapat menggunakan nilai pengembalian dari metode WaitAny untuk menentukan apakah token pembatalan yang mengaktifkannya. Operasi kemudian dapat keluar, atau melemparkan OperationCanceledException, sesuai dengan kebutuhan.
// Wait on the event if it is not signaled.
int eventThatSignaledIndex =
WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
new TimeSpan(0, 0, 20));
' Wait on the event if it is not signaled.
Dim waitHandles() As WaitHandle = {mre, token.WaitHandle}
Dim eventThatSignaledIndex =
WaitHandle.WaitAny(waitHandles, _
New TimeSpan(0, 0, 20))
System.Threading.ManualResetEventSlim dan System.Threading.SemaphoreSlim keduanya mendukung kerangka kerja pembatalan dalam metode mereka Wait . Anda dapat meneruskan CancellationToken ke metode tersebut, dan ketika pembatalan diminta, peristiwa tersebut aktif dan melempar OperationCanceledException.
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);
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)
Untuk contoh yang lebih lengkap, lihat Cara: Mendengarkan Permintaan Pembatalan yang Memiliki Handle Tunggu.
Mendengarkan Beberapa Token Secara Bersamaan
Dalam beberapa kasus, pendengar mungkin harus mendengarkan beberapa token pembatalan secara bersamaan. Misalnya, operasi yang dapat dibatalkan mungkin harus memantau token pembatalan internal selain token yang diteruskan secara eksternal sebagai argumen ke parameter metode. Untuk mencapai hal ini, buat sumber token tertaut yang dapat menggabungkan dua token atau lebih ke dalam satu token, seperti yang ditunjukkan dalam contoh berikut.
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();
}
}
}
}
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
Perhatikan bahwa Anda harus memanggil Dispose pada sumber token yang ditautkan setelah Anda selesai menggunakannya. Untuk contoh yang lebih lengkap, lihat Cara: Mendengarkan Beberapa Permintaan Pembatalan.
Kerja Sama Antara Kode Perpustakaan dan Kode Pengguna
Kerangka kerja pembatalan terpadu memungkinkan kode pustaka membatalkan kode pengguna, serta memungkinkan kode pengguna membatalkan kode pustaka dengan cara yang kooperatif. Kerja sama yang lancar tergantung pada setiap sisi mengikuti pedoman ini:
Jika kode pustaka menyediakan operasi yang dapat dibatalkan, kode pustaka juga harus menyediakan metode publik yang menerima token pembatalan eksternal sehingga kode pengguna dapat meminta pembatalan.
Jika kode pustaka memanggil kode pengguna, kode pustaka harus menginterpretasikan OperationCanceledException (externalToken) sebagai pembatalan kooperatif, dan belum tentu sebagai pengecualian kegagalan.
Delegasi pengguna harus mencoba menanggapi permintaan pembatalan dari kode pustaka secara tepat waktu.
System.Threading.Tasks.Task dan System.Linq.ParallelEnumerable adalah contoh kelas yang mengikuti panduan ini. Untuk informasi selengkapnya, lihat Pembatalan Tugas dan Cara: Membatalkan Kueri PLINQ.