Связывание маркеров отмены
Я использую маркер отмены, который передается, так что моя служба может быть отключена чисто. У службы есть логика, которая пытается подключиться к другим службам, поэтому токен - хороший способ вырваться из этих циклов повтора, запущенных в отдельных потоках. Моя проблема в том, что мне нужно сделать вызов службе, которая имеет внутреннюю логику повтора, но вернуться после заданного периода, если попытка повтора не выполнена. Я хотел бы создать новый токен отмены с тайм-аутом, который сделает это для меня. Проблема заключается в том, что мой новый токен не связан с маркером "master", поэтому, когда главный токен отменен, мой новый токен будет по-прежнему оставаться в живых до тех пор, пока не истечет время ожидания или не будет создано соединение, и оно будет возвращено. То, что я хотел бы сделать, - это связать два токена вместе, так что, когда главный будет отменен, новый будет отменен. Я попытался использовать метод CancellationTokenSource.CreateLinkedTokenSource
, но когда мой новый токен тайм-аута, он также отменил мастер-токен. Есть ли способ делать то, что мне нужно делать с токенами, или потребует изменений в логике повтора (возможно, это не будет легко)
Вот что я хочу сделать:
Мастер-токен - передал различные функции, чтобы служба могла отключиться.
Временный токен - передается на одну функцию и устанавливается на тайм-аут через одну минуту
Если мастер-токен отменен, временный токен также должен быть отменен.
Когда срок действия временного токена истекает, он НЕ должен отменять мастер-токен.
Ответы
Ответ 1
Вы хотите использовать CancellationTokenSource.CreateLinkedTokenSource
. Это позволяет иметь "родительский" и "дочерний" CancellationTokenSource
es. Вот простой пример:
var parentCts = new CancellationTokenSource();
var childCts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token);
childCts.CancelAfter(1000);
Console.WriteLine("Cancel child CTS");
Thread.Sleep(2000);
Console.WriteLine("Child CTS: {0}", childCts.IsCancellationRequested);
Console.WriteLine("Parent CTS: {0}", parentCts.IsCancellationRequested);
Console.WriteLine();
parentCts.Cancel();
Console.WriteLine("Cancel parent CTS");
Console.WriteLine("Child CTS: {0}", childCts.IsCancellationRequested);
Console.WriteLine("Parent CTS: {0}", parentCts.IsCancellationRequested);
Вывод ожидаемого результата:
Отменить ребенка CTS
Ребенок CTS: истинный
Родительский CTS: False
Отменить родительский CTS
Ребенок CTS: истинный
Родительский CTS: True
Ответ 2
Как i3arnon уже ответил, вы можете сделать это с помощью CancellationTokenSource.CreateLinkedTokenSource()
. Я хочу попытаться показать, как использовать такой токен, когда вы хотите отличить отмену общей задачи от отмены дочерней задачи без отмены общей задачи.
async Task MyAsyncTask(
CancellationToken ct)
{
// Keep retrying until the master process is cancelled.
while (true)
{
// Ensure we cancel ourselves if the parent is cancelled.
ct.ThrowIfCancellationRequested();
var childCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
// Set a timeout because sometimes stuff gets stuck.
childCts.CancelAfter(TimeSpan.FromSeconds(32));
try
{
await DoSomethingAsync(childCts.Token);
}
// If our attempt timed out, catch so that our retry loop continues.
// Note: because the token is linked, the parent token may have been
// cancelled. We check this at the beginning of the while loop.
catch (OperationCancelledException) when (childCts.IsCancellationRequested)
{
}
}
}
Когда срок действия временного токена истекает, он НЕ должен отменять мастер-токен.
Обратите внимание, что подпись MyAsyncTask()
s принимает CancellationToken
, а не CancellationTokenSource
. Поскольку метод имеет доступ только к элементам на CancellationToken
, он не может случайно отменить главный/родительский токен. Я рекомендую вам организовать ваш код таким образом, чтобы CancellationTokenSource
главной задачи отображался как можно меньше кода. В большинстве случаев это можно сделать, передав CancellationTokenSource.Token
в методы вместо того, чтобы передавать ссылку на CancellationTokenSource
.
Я не исследовал, но может быть способ с чем-то вроде отражения, чтобы принудительно отменить CancellationToken
без доступа к его CancellationTokenSource
. Надеюсь, это невозможно, но если это было возможно, это будет считаться плохой практикой и вообще не о чем беспокоиться.
Ответ 3
Если у вас есть только CancellationToken
, а не CancellationTokenSource
, тогда все еще можно создать связанный токен отмены. Вы бы просто использовали метод Register
чтобы вызвать отмену (псевдо) дочернего элемента:
var child = new CancellationTokenSource();
token.Register(child.Cancel);
Вы можете делать все, что вы обычно делаете с CancellationTokenSource
. Например, вы можете отменить его через некоторое время и даже перезаписать свой предыдущий токен.
child.CancelAfter(cancelTime);
token = child.Token;