Почему CancellationToken отделен от CancellationTokenSource?


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

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

спасибо!

6 102

6 ответов:

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

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

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

У меня был точно такой же вопрос и я хотел понять смысл этой конструкции.

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

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

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

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

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

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

CancellationToken API

CancellationTokenSource API

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

Я думаю, что текущий дизайн класса следует философии "pit of success", он помогает разработчикам создавать лучшие компоненты, которые могут обрабатывать отмену задач, а затем совместно использовать их для создания сложных рабочих процессов.

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

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

они разделены не по техническим причинам, а по смысловым. Если вы посмотрите на реализацию CancellationToken под ILSpy, вы найдете это просто обертка вокруг CancellationTokenSource (и, таким образом, не отличается по производительности, чем передача ссылки).

они обеспечивают это разделение функций, чтобы сделать вещи более предсказуемыми: когда вы передаете метод a CancellationToken, вы знаете, что вы все еще единственный, кто может отменить его. Конечно, метод все еще может бросить TaskCancelledException, но CancellationToken сам - и любые другие методы, ссылающиеся на тот же маркер-будет оставаться безопасным.

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

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

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

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

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

моя "теория" заключается в том, что роль CancellationToken это обеспечить потокобезопасную обертку, чтобы избежать утверждение.

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

а с CancellationToken struct вы есть копия каждого обратного вызова-очереди для каждого потока, так что никаких разногласий.

Это чистая спекуляция, и я могу быть совершенно неправ и/или могут быть другие веские причины для этого дизайна.

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