Получить отмену заданий
Могу ли я получить CancellationToken
, который был передан конструктору Task
во время выполнения действия задачи. Большинство образцов выглядят следующим образом:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task myTask = Task.Factory.StartNew(() =>
{
for (...)
{
token.ThrowIfCancellationRequested();
// Body of for loop.
}
}, token);
Но что, если мое действие не лямбда, а метод, помещенный в другой класс, и у меня нет прямого доступа к token
? Единственный способ - передать token
как состояние?
Ответы
Ответ 1
Но что, если мое действие не лямбда, а метод, помещенный в другой класс, и у меня нет прямого доступа к токену? Единственный способ - передать токен как состояние?
Да, в этом случае вам нужно будет передать токен в виде состояния или включить в какой-либо другой тип, который вы используете как состояние.
Это требуется только в том случае, если вы планируете использовать CancellationToken
внутри метода. Например, если вам нужно позвонить token.ThrowIfCancellationRequested()
.
Если вы используете только токен, чтобы предотвратить запланированный метод, он не требуется.
Ответ 2
Можно ли получить CancellationToken, который был передан конструктору задачи во время выполнения действия задачи?
Нет, вы не можете получить его непосредственно из объекта Task
, no.
Но что, если мое действие не лямбда, а метод, помещенный в другой класс, и у меня нет прямого доступа к токену? Единственный способ - передать токен как состояние?
Это два варианта, да. Есть и другие. (Возможно, не включительный список.)
-
Вы можете закрыть маркер отмены анонимным методом
-
Вы можете передать его как состояние
-
Вы можете убедиться, что экземпляр, используемый для делегата задачи, имеет поле экземпляра, которое хранится на маркере отмены или удерживается на некотором объекте, который хранится на токене и т.д.
-
Вы можете выставить токен, хотя какая-то другая более крупная область как состояние, то есть как публичное статическое поле (в большинстве случаев это плохая практика, но иногда это может быть применимо).
Ответ 3
Как утверждают другие ответы, вы можете передать токен в качестве параметра в свой метод. Однако важно помнить, что вы все равно хотите передать его в Task
. Task.Factory.StartNew( () => YourMethod(token), token)
, например.
Это гарантирует, что:
-
Task
не будет работать, если отмена произойдет до выполнения Task
(это хорошая оптимизация)
-
An OperationCanceledException
, вызванный вызываемым методом, правильно переводит задачу в состояние Canceled
Ответ 4
Существует очень простое решение:
class CancelingTasks
{
private static void Foo(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(100);
Console.Write(".");
}
}
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken tok = source.Token;
tok.Register(() =>
{
Console.WriteLine("Cancelled.");
});
Task t = new Task(() =>
{
Foo(tok);
}, tok);
t.Start();
Console.ReadKey();
source.Cancel();
source.Dispose();
Console.WriteLine("Main program done, press any key.");
Console.ReadKey();
}
}
Ответ 5
Вы можете получить CancellationToken, обратившись к внутренним полям с отражением.
public CancellationToken GetCancellationToken(Task task)
{
object m_contingentProperties = task
.GetType()
.GetField("m_contingentProperties",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.GetValue(task);
object m_cancellationToken = m_contingentProperties
.GetType()
.GetField("m_cancellationToken",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.GetValue(m_contingentProperties);
return (CancellationToken)m_cancellationToken;
}
Подсказка: вы можете искать такие вещи самостоятельно ILSpy.
Ответ 6
Это похоже на работу:
public static CancellationToken GetCancellationToken(this Task task)
{
return new TaskCanceledException(task).CancellationToken;
}
Это может быть необходимо для того, чтобы помощники задач общего назначения сохраняли CancellationToken отмененной задачи (я прибыл сюда, пытаясь заставить метод Jon Skeet WithAllExceptions сохранить токен).
Ответ 7
Когда мы смотрим на исходный код ссылки на класс Task, мы видим, что токен отмены хранится во внутреннем классе: ContingentProperties
https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,90a9f91ddd80b5cc
Цель состоит в том, чтобы избежать доступа к этим свойствам, и эти свойства не всегда необходимы.
internal class ContingentProperties
{
// Additional context
internal ExecutionContext m_capturedContext; // The execution context to run the task within, if any.
// Completion fields (exceptions and event)
internal volatile ManualResetEventSlim m_completionEvent; // Lazily created if waiting is required.
internal volatile TaskExceptionHolder m_exceptionsHolder; // Tracks exceptions, if any have occurred
// Cancellation fields (token, registration, and internally requested)
internal CancellationToken m_cancellationToken; // Task cancellation token, if it has one
internal Shared<CancellationTokenRegistration> m_cancellationRegistration; // Task registration with the cancellation token
internal volatile int m_internalCancellationRequested; // Its own field because threads legally ---- to set it.
// Parenting fields
// # of active children + 1 (for this task itself).
// Used for ensuring all children are done before this task can complete
// The extra count helps prevent the ---- for executing the final state transition
// (i.e. whether the last child or this task itself should call FinishStageTwo())
internal volatile int m_completionCountdown = 1;
// A list of child tasks that threw an exception (TCEs don't count),
// but haven't yet been waited on by the parent, lazily initialized.
internal volatile List<Task> m_exceptionalChildren;
/// <summary>
/// Sets the internal completion event.
/// </summary>
internal void SetCompleted()
{
var mres = m_completionEvent;
if (mres != null) mres.Set();
}
/// <summary>
/// Checks if we registered a CT callback during construction, and deregisters it.
/// This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed
/// successfully or with an exception.
/// </summary>
internal void DeregisterCancellationCallback()
{
if (m_cancellationRegistration != null)
{
// Harden against ODEs thrown from disposing of the CTR.
// Since the task has already been put into a final state by the time this
// is called, all we can do here is suppress the exception.
try { m_cancellationRegistration.Value.Dispose(); }
catch (ObjectDisposedException) { }
m_cancellationRegistration = null;
}
}
}