Почему CancellationToken отличается от CancellationTokenSource?

137

Я ищу объяснение того, почему .NET CancellationTokenstruct была введена в дополнение к 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, и для каждого токена проверка отмены является доступом к локальному полю?

Учитывая, что в обоих случаях достаточно volatile bool без блокировки, я все еще не понимаю, почему это будет быстрее.

Спасибо!

Андрей Таранцов
источник

Ответы:

110

Я участвовал в разработке и реализации этих классов.

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

Если оставить дверь открытой для некорректных API-интерфейсов, то полезность схемы отмены может быстро упасть.

CancellationTokenSource == "триггер отмены", плюс генерирует связанных слушателей

CancellationToken == "слушатель отмены"

Майк Лидделл
источник
7
Большое спасибо! Внутренние знания действительно ценятся.
Андрей Таранцов
stackoverflow.com/questions/39077497/… У вас есть идеи, каким должен быть тайм-аут по умолчанию для входного потока StreamSocket? Когда я использую cancellationtoken для отмены операции чтения, он также закрывает соответствующий сокет. Есть ли способ решить эту проблему?
sam18
@Mike - Просто любопытно: почему вы не можете вызвать что-то вроде ThrowIfCancellationRequested () на CTS, как вы можете на CT?
rory.ap
У них разные обязанности, и писать cts.Token.ThrowIfCancellationRequested () не слишком многословно, поэтому мы не добавляли API прослушивателя непосредственно в CTS.
Майк Лидделл
@Mike Почему cancellationtoken - это структура, а не класс? Я не вижу никаких преимуществ в производительности
Neir0
85

У меня был точный вопрос, и я хотел понять смысл этого дизайна.

В принятом ответе было правильное обоснование. Вот подтверждение от команды, которая разработала эту функцию (выделено мной):

Основу фреймворка CancellationTokenсоставляют два новых типа: A - это структура, представляющая «потенциальный запрос на отмену». Эта структура передается в вызовы метода в качестве параметра, и метод может опрашивать ее или регистрировать обратный вызов, который запускается при запросе отмены. A CancellationTokenSource- это класс, который предоставляет механизм для инициирования запроса на отмену и имеет Token свойство для получения связанного токена. Было бы естественно объединить эти два класса в один, но этот дизайн позволяет четко разделить две ключевые операции (инициирование запроса на отмену против наблюдения и ответа на отмену). В частности, методы, которые принимают только a, CancellationTokenмогут отслеживать запрос на отмену, но не могут его инициировать.

Ссылка: .NET 4 Cancellation Framework

На мой взгляд, CancellationTokenкрайне критично то, что можно только наблюдать за состоянием, а не изменять его. Вы можете раздать жетон как конфету и никогда не беспокоиться, что кто-то другой, кроме вас, отменит его. Он защищает вас от враждебного стороннего кода. Да, шансы невелики, но лично мне эта гарантия нравится.

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

Давайте посмотрим на общедоступный API для обоих этих классов.

CancellationToken API

CancellationTokenSource API

Если бы вы скомбинировали их при написании LongRunningFunction, я увижу такие методы, как несколько перегрузок «Отмена», которые мне не следует использовать. Лично мне тоже не нравится метод Dispose.

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

Позвольте мне задать вам вопрос, вы задавались вопросом, для чего нужен токен.Регистрация? Для меня это не имело смысла. А потом я прочитал « Отмена в управляемых потоках», и все стало предельно ясно.

Я считаю, что дизайн системы отмены в TPL абсолютно на высшем уровне.

SolutionYogi
источник
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, вы знаете, что по-прежнему единственный, кто может его отменить. Конечно, метод все еще может генерировать a TaskCancelledException, но CancellationTokenсам по себе - и любые другие методы, ссылающиеся на тот же токен - останутся безопасными.

Кори Нельсон
источник
Спасибо! Моя реакция на мгновение заключается в том, что семантически это сомнительный подход наравне с предоставлением IReadOnlyList. Однако это звучит очень правдоподобно, поэтому примите ваш ответ.
Андрей Таранцов
1
Поразмыслив, я обнаружил, что сомневаюсь в правдоподобности. Разве тогда не было бы разумнее предоставить интерфейс ICancellationToken, который будет реализовывать CancellationTokenSource? Я бы хотел, чтобы кто-нибудь из команды .NET вмешался.
Андрей Таранцов
Это все еще может привести к тому, что хитрый программист переключится на CancellationTokenSource. Можно подумать, что можно просто сказать «не делай этого», но люди (включая меня!) Иногда делают это, чтобы получить доступ к некоторым скрытым функциям, и это произойдет. По крайней мере, это моя текущая теория.
Кори Нельсон
1
В большинстве случаев API-интерфейсы разрабатываются не так, если только это не связано с безопасностью. Я бы предпочел сделать ставку на какое-нибудь другое объяснение, например, «оставить свои возможности открытыми для оптимизации производительности в будущем». Но, тем не менее, ваш пока лучший.
Андрей Таранцов
Эй, я надеюсь, вы извините, что я переназначаю принятый ответ человеку, участвовавшему в реализации. Хотя вы оба говорите одно и то же, я думаю, ТАК выигрывает от того, что окончательный ответ на высоте.
Андрей Таранцов
10

Это CancellationTokenструктура, поэтому может существовать много копий из-за передачи ее методам.

В CancellationTokenSourceустанавливает состояние всех копий токена при вызове Cancelна источник. См. Эту страницу MSDN

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

Эрно
источник
1
Спасибо. Обратите внимание, что в другом ответе говорится, что технически (в IL) CancellationTokenSource не устанавливает состояние токенов; вместо этого токены оборачивают фактическую ссылку на CancellationTokenSource и просто обращаются к ней, чтобы проверить отмену. Во всяком случае, скорость здесь можно только потерять.
Андрей Таранцов
+1. Я не понимаю если это тип значения, поэтому каждый метод имеет собственное значение (отдельное значение). Итак, как метод узнает, была ли вызвана отмена? он НЕ имеет ссылки на TokenSource. все, что может увидеть метод, - это тип локального значения, например "5". Вы можете объяснить ?
Рой Намир
1
@RoyiNamir: у каждого CancellationToken есть личная ссылка "m_source" типа CancellationTokenSource
Andreyul
2

Это CancellationTokenSource"вещь", которая вызывает отмену по любой причине. Ему нужен способ «отправить» эту отмену всем CancellationTokenвыданным им. Вот как, например, ASP.NET может отменять операции, когда запрос прерывается. Каждый запрос имеет объект, CancellationTokenSourceкоторый перенаправляет отмену всем выпущенным им токенам.

Это отлично подходит для модульного тестирования BTW - создайте свой собственный источник токена отмены, получите токен, вызовите Cancelисточник и передайте токен в свой код, который должен обрабатывать отмену.

n8wrl
источник
Спасибо - но обе обязанности (которые являются очень связаны) могут быть приведены к одному классу. На самом деле не объясняет, почему они разделены.
Андрей Таранцов