Передавайте Состояние. Окончание
Начало
Продолжение
CancellationToken.Register
Вы можете зарегистрировать функцию обратного вызова, которая будет вызвана при отмене токена:
CancellationToken ct;
TaskCompletionSource tcs = new ();
ct.Register(() => tcs.TrySetCanceled());
Та же проблема. Попытка добавить модификатор static приведёт к ошибке:
ct.Register(static () => tcs.TrySetCanceled());
Однако, если создатель функции подумал о производительности, он предоставит перегрузку, принимающую состояние.
…
ct.Register(
static (state) =>
((TaskCompletionSource)state).TrySetCanceled(),
tcs); // Состояние передаётся в последнем параметре
Да, здесь требуется привести состояние к нужному типу и правильно его передать. В обычном бизнес-приложении это может быть необязательно. Но, например, в RavenDB, распределённой БД, созданной на .NET и использующей CancellationToken из BCL, PR #21205 предлагает такие улучшения, используя перегрузку с передачей состояния.
Принцип улучшений прост:
- Вы замечаете использование Register с нестатическии методом.
- Делаете лямбду статической и ждёте ошибок компилятора.
- При возможности используйте перегрузку с передачей состояния.
Task.ContinueWith
Аналогичный подход, как и с CancellationToken, можно использовать с продолжениями, которые можно прикреплять к задачам вручную. Иногда необходимо прикрепить продолжение к задаче без использования сложного механизма async-await. В таком случае можно использовать метод ContinueWith:
Task connection;
TaskCompletionSource tcs;
connection.ContinueWith ( (t) => {
if (t.IsFaulted)
tcs.TrySetException(t.Exception);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(null);
});
Мы можем следовать тому же подходу, сначала сделав метод статическим. Затем, обратив внимание на ошибку компиляции, исправим её с помощью перегрузки метода, передающей состояние, и приведения типов:
Task connection;
TaskCompletionSource tcs;
// лямбда теперь статическая
connection.ContinueWith ( static (t, state) => {
// приводим к типу продолжения
var c = (TaskCompletionSource) state;
if (t.IsFaulted)
c.TrySetException(t.Exception);
else if (t.IsCanceled)
c.TrySetCanceled();
else
c.TrySetResult(null);
}, tcs); // передаём состояние
Итого
Захват переменных в лямбдах может стать серьёзной проблемой на горячих путях приложений .NET. К счастью, существуют контрмеры в виде статических лямбда-выражений и перегрузок, передающих состояние, которые позволяют устранить эти проблемы.
Источник: https://blog.scooletz.com/2025/10/08/pass-the-state