Чому CancellationToken є окремим від CancellationTokenSource?


137

Я шукаю обґрунтування того, чому .NET CancellationTokenструктура була введена крім CancellationTokenSourceкласу. Я розумію, як слід використовувати API, але хочу також зрозуміти, чому він створений саме так.

Тобто, чому ми маємо:

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

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

замість того, щоб безпосередньо пройти CancellationTokenSourceнавколо:

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

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

Це оптимізація ефективності, заснована на тому, що перевірки стану скасування відбуваються частіше, ніж передача лексеми навколо?

Отже, це CancellationTokenSourceможе відслідковувати та оновлювати CancellationTokens, і для кожного маркера перевірка скасування - це локальний доступ до поля?

Зважаючи на те, що мінливий булінг без блокування в обох випадках, я все ще не можу зрозуміти, чому це було б швидше.

Дякую!

Відповіді:


110

Я брав участь у розробці та реалізації цих занять.

Коротка відповідь - « розділення проблем ». Цілком вірно, що існують різні стратегії впровадження і що деякі простіші принаймні щодо типової системи та початкового навчання. Однак CTS та CT призначені для використання у багатьох сценаріях (таких як глибокі стеки бібліотек, паралельні обчислення, асинхронізація тощо), і таким чином було розроблено з урахуванням багатьох складних випадків використання. Це дизайн, покликаний заохочувати успішні візерунки та відбивати анти-шаблони, не приносячи шкоди продуктивності.

Якщо двері залишилися відкритими для недоброзичливих API, тоді корисність дизайну скасування може бути швидко підірвана.

CancellationTokenSource == "тригер скасування", а також створює пов'язаних слухачів

CancellationToken == "слухач скасування"


7
Дуже дякую! Знання зсередини дуже цінуються.
Андрій Таранцов

stackoverflow.com/questions/39077497/… Чи маєте ви уявлення про те, яким повинен бути затримка для введення потоку StreamSocket за замовчуванням? Коли я використовую метод Скасування для скасування операції зчитування, він також закриває відповідний сокет. Чи може бути якийсь спосіб подолати це питання?
sam18

@Mike - Просто цікаво: а чому ви не можете назвати щось на зразок ThrowIfCancellationRequested () на CTS, як ви можете на КТ?
rory.ap

Вони мають різні обов'язки, і це не дуже багатослівно, щоб писати cts.Token.ThrowIfCancellationRequested (), тому ми не додавали API слухача безпосередньо на CTS.
Майк Лідделл

@Mike Чому cancellationtoken - це структура, а не клас? Я не бачу жодних переваг у виконанні
Neir0,

85

У мене було точне питання, і я хотів зрозуміти обґрунтування цієї конструкції.

Прийнята відповідь отримала обґрунтування точно. Ось підтвердження від команди, яка розробила цю функцію (моє наголос):

Основу основи складають два нових типи: A CancellationToken- це структура, яка представляє "потенційний запит на скасування". Ця структура передається у виклики методу як параметр, і метод може опитуватись на ній або реєструвати зворотний виклик, який буде знятий, коли вимагається скасування. A CancellationTokenSource- клас, який забезпечує механізм ініціювання запиту на скасування, і він має Token властивість отримувати пов'язаний маркер. Було б закономірно об'єднати ці два класи в один, але ця конструкція дозволяє дві ключові операції (ініціювання запиту на скасування проти спостереження та відповідь на скасування) чітко відокремлені. Зокрема, методи, які беруть лише за собою, CancellationTokenможуть виконувати запит на скасування, але не можуть його ініціювати.

Посилання: .NET 4 Рамка скасування

На мою думку, той факт, що CancellationTokenможна лише спостерігати за станом, а не змінювати його, є надзвичайно критичним. Ви можете роздати жетон як цукерку і ніколи не турбуватися, що хтось інший, крім вас, скасує його. Він захищає вас від ворожих сторонніх кодів. Так, шанси невеликі, але мені особисто це подобається.

Я також вважаю, що це робить API чистішим і уникає випадкових помилок та сприяє кращому дизайну компонентів.

Давайте розглянемо публічний API для обох цих класів.

CancellationToken API

API CancellationTokenSource

Якщо ви збираєте їх поєднувати, під час написання LongRunningFunction я побачу такі методи, як ті багаторазові перевантаження "Скасувати", які я не повинен використовувати. Особисто я ненавиджу бачити також метод утилізації.

Я думаю, що сучасний дизайн класу дотримується філософії «ями успіху», він спрямовує розробників на створення кращих компонентів, які можуть обробляти Taskскасування, а потім численні інструменти для їх створення для створення складних робочих процесів.

Дозвольте мені задати вам питання, чи замислювались ви, яка мета токена? Зареєструйтесь? Для мене це не мало сенсу. А потім я прочитав Скасування в керованих нитках і все стало кристально зрозумілим.

Я вважаю, що дизайн рамки скасування в TPL абсолютно неперевершений.


2
Якщо вам цікаво, як CancellationTokenSourceможе насправді ініціювати запит на скасування в асоційованому токені (маркер не може зробити це сам): у CancellationToken є цей внутрішній конструктор: internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }і ця властивість: public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }CancellationTokenSource використовує внутрішній конструктор, тому маркер має посилання на джерело (m_source)
chviLadislav

65

Вони є окремими не з технічних причин, а семантичних. Якщо ви подивитесь на реалізацію CancellationTokenпід ILSpy, ви побачите, що це просто обгортка навколо CancellationTokenSource(і, отже, не відрізняється ефективністю, ніж обхід посилання).

Вони забезпечують таке розділення функціональності, щоб зробити речі більш передбачуваними: коли ви передаєте метод a CancellationToken, ви знаєте, що ви все ще єдиний, хто може його скасувати. Звичайно, метод все-таки міг би викинути TaskCancelledException, але CancellationTokenсам - а також будь-які інші методи, що посилаються на той же маркер - залишався б безпечним.


Дякую! Моя реакція моргання полягає в тому, що семантично це сумнівний підхід, нарівні із наданням IReadOnlyList. Однак це звучить дуже правдоподібно, тому приймаю вашу відповідь.
Андрій Таранцов

1
Після подальшого роздуму я опиняюся під питанням правдоподібності. Хіба не було б більше сенсу надати інтерфейс ICancellationToken, який реалізуватиме CancellationTokenSource? Бажаю, щоб хтось із .NET команди
задзвонив

Це все ще може призвести до хитрого програміста, який підкоряється CancellationTokenSource. Ви б могли подумати, що ви можете просто сказати "не робіть цього", але люди (включаючи мене!) Іноді роблять ці речі все одно, щоб отримати якийсь прихований функціонал, і це станеться. Принаймні, це моя сучасна теорія.
Кори Нельсон

1
У більшості випадків ви не проектуєте API, якщо це не стосується безпеки. Я б скоріше зробив ставку на якесь інше пояснення, наприклад, "підтримуючи свої параметри відкритими для оптимізації ефективності в майбутньому". Але все-таки ваша найкраща досі.
Андрій Таранцов

Ей, я сподіваюся, ви вибачте мене, повторно призначивши прийняту відповідь особі, яка бере участь у реалізації. Хоча ви обидва говорите одне і те ж, я думаю, що ТАК виграє від того, що остаточна відповідь знаходиться на вершині.
Андрій Таранцов

10

Структура CancellationTokenє стільки копій, що може існувати завдяки передачі їх методам.

В CancellationTokenSourceвстановлює стан всіх копій токена при виклику Cancelна джерело. Дивіться цю сторінку MSDN

Причиною для дизайну може бути лише питання розділення проблем та швидкості структури.


1
Дякую. Зверніть увагу, що інша відповідь говорить про те, що технічно (в IL) технічний параметр CancellationTokenSource не встановлює стан жетонів; натомість маркери містять фактичне посилання на CancellationTokenSource та просто отримують доступ до нього, щоб перевірити на скасування. Якщо що-небудь, швидкість тут можна втратити лише.
Андрій Таранцов

+1. Я не розумію. якщо його тип значення, то кожен метод має своє значення (окреме значення). тож як метод знає, чи було викликано скасування? він НЕ має посилання на TokenSource. весь метод, який можна побачити, є локальним типом значення типу "5". ти можеш пояснити ?
Royi Namir

1
@RoyiNamir: У кожного CancellationToken є приватне посилання "m_source" типу CancellationTokenSource
Andreyul

2

Це CancellationTokenSource"річ", яка видає скасування з будь-якої причини. Він потребує способу "відправки" цього скасування для всіх CancellationTokenвиданих ним. Так, наприклад, ASP.NET може скасувати операції, коли запит перервано. Кожен запит має таке, CancellationTokenSourceщо пересилає скасування всім виданим жетонам.

Це відмінно підходить для тестування одиниць BTW - створити власне джерело токенів скасування, отримати токен, зателефонувати Cancelна джерело та передати маркер своєму коду, який повинен обробляти скасування.


Спасибі - але обидві обов'язки (які є дуже пов'язані) можуть бути приведені до одного класу. Насправді не пояснює, чому вони відокремлені.
Андрій Таранцов
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.