Mengapa PembatalanToken terpisah dari PembatalanTokenSource?

137

Saya sedang mencari alasan mengapa. NET CancellationTokenstruct diperkenalkan di samping CancellationTokenSourcekelas. Saya mengerti bagaimana API akan digunakan, tetapi ingin juga memahami mengapa itu dirancang seperti itu.

Yaitu, mengapa kita memiliki:

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts.Token);

...
public void SomeCancellableOperation(CancellationToken token) {
    ...
    token.ThrowIfCancellationRequested();
    ...
}

bukannya langsung lewat CancellationTokenSourceseperti:

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts);

...
public void SomeCancellableOperation(CancellationTokenSource cts) {
    ...
    cts.ThrowIfCancellationRequested();
    ...
}

Apakah ini optimasi kinerja berdasarkan pada fakta bahwa pembatalan pemeriksaan pembatalan terjadi lebih sering daripada melewati token?

Sehingga CancellationTokenSourcedapat melacak dan memperbarui CancellationTokens, dan untuk setiap token cek pembatalan adalah akses bidang lokal?

Mengingat bahwa bool volatil tanpa penguncian sudah cukup dalam kedua kasus, saya masih tidak bisa melihat mengapa itu akan lebih cepat

Terima kasih!

Andrey Tarantsov
sumber

Jawaban:

110

Saya terlibat dalam desain dan implementasi kelas-kelas ini.

Jawaban singkatnya adalah " pemisahan keprihatinan ". Memang benar bahwa ada berbagai strategi implementasi dan bahwa beberapa lebih sederhana setidaknya mengenai jenis sistem dan pembelajaran awal. Namun, CTS dan CT dimaksudkan untuk digunakan dalam banyak skenario besar (seperti tumpukan perpustakaan dalam, perhitungan paralel, async, dll) dan dengan demikian dirancang dengan banyak kasus penggunaan yang kompleks dalam pikiran. Ini adalah desain yang dimaksudkan untuk mendorong pola yang sukses dan mencegah pola-anti tanpa mengorbankan kinerja.

Jika pintu dibiarkan terbuka untuk API yang rusak, maka kegunaan desain pembatalan dapat dengan cepat terkikis.

CancellationTokenSource == "pemicu pembatalan", plus menghasilkan pendengar yang tertaut

CancellationToken == "pendengar pembatalan"

Mike Liddell
sumber
7
Terima kasih banyak! Pengetahuan orang dalam sangat dihargai.
Andrey Tarantsov
stackoverflow.com/questions/39077497/… Apakah Anda tahu apa yang seharusnya menjadi batas waktu default untuk aliran input StreamSocket? Ketika saya menggunakan cancellationtoken untuk membatalkan operasi baca, itu juga menutup soket yang bersangkutan. Bisakah ada cara untuk mengatasi masalah ini?
sam18
@ Mike - Hanya ingin tahu: kenapa Anda tidak dapat memanggil sesuatu seperti ThrowIfCancellationRequested () pada CTS seperti Anda dapat menggunakan CT?
rory.ap
Mereka memiliki tanggung jawab yang berbeda dan tidak terlalu bertele-tele untuk menulis cts.Token.ThrowIfCancellationRequested () sehingga kami tidak menambahkan API pendengar langsung pada CTS.
Mike Liddell
@ Mike Mengapa cancellationtoken adalah struct dan bukan kelas? Saya tidak melihat keuntungan kinerja apa pun
Neir0
85

Saya punya pertanyaan yang tepat dan saya ingin memahami alasan di balik desain ini.

Jawaban yang diterima mendapatkan alasan yang tepat. Berikut konfirmasi dari tim yang merancang fitur ini (penekanan pada saya):

Dua tipe baru membentuk dasar kerangka kerja: A CancellationTokenadalah struct yang mewakili 'permintaan potensial untuk pembatalan'. Struct ini diteruskan ke pemanggilan metode sebagai parameter dan metode ini dapat melakukan polling atau mendaftarkan callback untuk dipecat ketika pembatalan diminta. A CancellationTokenSourceadalah kelas yang menyediakan mekanisme untuk memulai permintaan pembatalan dan memiliki Token properti untuk mendapatkan token terkait. Adalah wajar untuk menggabungkan kedua kelas ini menjadi satu, tetapi desain ini memungkinkan dua operasi utama (memulai permintaan pembatalan vs. mengamati dan menanggapi pembatalan) untuk dipisahkan secara bersih. Secara khusus, metode yang hanya mengambil CancellationTokendapat mengamati permintaan pembatalan tetapi tidak dapat memulai satu.

Tautan: .NET 4 Framework Framework

Menurut saya, fakta yang CancellationTokenhanya bisa mengamati negara dan tidak mengubahnya, sangat kritis. Anda dapat membagikan token seperti permen dan tidak pernah khawatir bahwa orang lain, selain Anda, akan membatalkannya. Ini melindungi Anda dari kode pihak ketiga yang bermusuhan. Ya, peluangnya tipis, tapi saya pribadi suka jaminan itu.

Saya juga merasa bahwa itu membuat API lebih bersih dan menghindari kesalahan tak disengaja dan mempromosikan desain komponen yang lebih baik.

Mari kita lihat API publik untuk kedua kelas ini.

CancelledToken API

API PembatalanTokenSource

Jika Anda menggabungkannya, saat menulis LongRunningFunction, saya akan melihat metode seperti beberapa overload 'Batalkan' yang seharusnya tidak saya gunakan. Secara pribadi, saya benci melihat metode Buang juga.

Saya pikir desain kelas saat ini mengikuti filosofi 'lubang keberhasilan', itu memandu pengembang untuk membuat komponen yang lebih baik yang dapat menangani Taskpembatalan dan kemudian menyatukan mereka dalam berbagai cara untuk membuat alur kerja yang rumit.

Izinkan saya mengajukan pertanyaan, apakah Anda bertanya-tanya apa tujuan token? Itu tidak masuk akal bagi saya. Dan kemudian saya membaca Pembatalan di Thread yang Dikelola dan semuanya menjadi sangat jelas.

Saya percaya bahwa Desain Kerangka Pembatalan di TPL benar-benar kedudukan tertinggi.

SolutionYogi
sumber
2
Jika Anda bertanya-tanya bagaimana CancellationTokenSourcesebenarnya dapat memulai permintaan membatalkan pada token yang terkait (token tidak dapat melakukannya sendiri): The CancellToken memiliki konstruktor internal ini: internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }dan properti ini: public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }The CancellTokenSource menggunakan konstruktor internal, sehingga token memiliki referensi ke source (m_source)
chviLadislav
65

Mereka terpisah bukan karena alasan teknis tetapi yang semantik. Jika Anda melihat implementasi di CancellationTokenbawah ILSpy, Anda akan menemukan itu hanya pembungkus CancellationTokenSource(dan dengan demikian tidak ada perbedaan kinerja-bijaksana daripada membagikan referensi).

Mereka menyediakan pemisahan fungsi ini untuk membuat hal-hal lebih dapat diprediksi: ketika Anda melewati suatu metode CancellationToken, Anda tahu Anda masih satu-satunya yang dapat membatalkannya. Tentu, metode ini masih bisa melempar TaskCancelledException, tetapi metode CancellationTokenitu sendiri - dan metode lain yang merujuk token yang sama - akan tetap aman.

Cory Nelson
sumber
Terima kasih! Reaksi kedip saya adalah bahwa, secara semantik, ini adalah pendekatan yang dipertanyakan, setara dengan menyediakan IReadOnlyList. Kedengarannya sangat masuk akal, jadi terima jawaban Anda.
Andrey Tarantsov
1
Setelah berpikir lebih jauh, saya mendapati diri saya mempertanyakan masuk akal. Maka, tidakkah lebih masuk akal untuk menyediakan antarmuka ICancellationToken yang akan diterapkan oleh CancertokenSource? Saya berharap seseorang dari tim .NET akan berpadu.
Andrey Tarantsov
Itu mungkin masih menghasilkan casting programmer licik untuk CancellationTokenSource. Anda akan berpikir Anda bisa mengatakan "jangan lakukan itu", tetapi orang-orang (termasuk saya!) Kadang-kadang melakukan hal-hal ini untuk mendapatkan beberapa fungsi tersembunyi, dan itu akan terjadi. Setidaknya itulah teori saya saat ini.
Cory Nelson
1
Dalam kebanyakan kasus, Anda tidak merancang API, kecuali terkait keamanan. Saya lebih suka bertaruh pada beberapa penjelasan lain, seperti "menjaga pilihan mereka terbuka untuk optimasi kinerja di masa depan". Tapi tetap saja, milik Anda adalah yang terbaik sejauh ini.
Andrey Tarantsov
Hei, saya harap Anda akan memaafkan saya menugaskan kembali jawaban yang diterima untuk orang yang terlibat dalam implementasi. Meskipun Anda berdua mengatakan hal yang sama, saya pikir SO mendapat manfaat dari jawaban definitif yang ada di atas.
Andrey Tarantsov
10

Ini CancellationTokenadalah struct sehingga banyak salinan bisa ada karena meneruskannya ke metode.

Set CancellationTokenSourcemenetapkan status SEMUA salinan token saat memanggil Cancelsumber. Lihat halaman MSDN ini

Alasan untuk desain mungkin hanya masalah pemisahan kekhawatiran dan kecepatan struct.

Erno
sumber
1
Terima kasih. Perhatikan bahwa jawaban lain memberi tahu bahwa secara teknis (dalam IL), PembatalanTokenSource tidak menetapkan status token; sebagai gantinya, token membungkus referensi aktual untuk CancellingTokenSource, dan cukup mengaksesnya untuk memeriksa pembatalan. Jika ada, kecepatannya hanya bisa hilang di sini.
Andrey Tarantsov
+1. Saya tidak mengerti. jika ini merupakan tipe nilai sehingga setiap metode memiliki nilainya sendiri (nilai terpisah). jadi bagaimana metode mengetahui jika pembatalan telah dipanggil? itu TIDAK memiliki referensi ke TokenSource. semua metode dapat lihat adalah tipe nilai lokal seperti "5". dapatkah kamu menjelaskan
Royi Namir
1
@RoyiNamir: Setiap PembatalanToken memiliki referensi pribadi "m_source" dari jenis PembatalanTokenSource
Andreyul
2

The CancellationTokenSourceadalah 'hal' bahwa isu-isu pembatalan, untuk alasan apapun. Perlu cara untuk 'mengirim' pembatalan itu ke semua yang CancellationTokentelah dikeluarkan. Begitulah cara, misalnya, ASP.NET dapat membatalkan operasi ketika permintaan dibatalkan. Setiap permintaan memiliki CancellationTokenSourceyang meneruskan pembatalan ke semua token yang telah dikeluarkannya.

Ini bagus untuk pengujian unit BTW - buat sumber token pembatalan Anda sendiri, dapatkan token, panggil Cancelsumbernya, dan berikan token ke kode Anda yang harus menangani pembatalan tersebut.

n8wrl
sumber
Terima kasih - namun kedua tanggung jawab (yang secara sangat terkait) dapat diberikan untuk kelas yang sama. Tidak benar-benar menjelaskan mengapa mereka terpisah.
Andrey Tarantsov