4

У меня есть такой код (упрощенный здесь), который ожидает отделочные задачи:Как объединить TaskCompletionSource и CancellationTokenSource?

var task_completion_source = new TaskCompletionSource<bool>(); 
observable.Subscribe(b => 
    { 
     if (b) 
      task_completion_source.SetResult(true); 
    }); 
await task_completion_source.Task;  

Идея заключается в том, чтобы подписаться и ждать true в потоке булевы. Это завершает «задачу», и я могу двигаться дальше await.

Однако я хотел был бы отменить - но не подписку, но ожидающую. Я хотел бы передать маркер отмены (как-то) на task_completion_source, поэтому, когда я отменяю токен, await будет двигаться дальше.

Как это сделать?

Обновление: CancellationTokenSource является внешним в этом коде, и все, что у меня есть, это его токен.

+0

Что случилось с 'task_completion_source.SetCanceled'? Имейте в виду, что предполагается, что вы правильно справляетесь с отменой задачи :) – Luaan

+0

@ Luaan, это ничего плохого, но нет кода ** running **, который мог бы его выполнить. Все ждут чего-то - подписка ждет данных (может быть, нет), 'await' ждет завершения задачи. – astrowalker

+0

Если вы отмените процесс async через cancelationtoken, он вызовет исключение TaskCanceledException, которое, в свою очередь, закончит ожидание (вам нужно обработать исключение). –

ответ

8

Если я вас правильно понимаю, вы можете сделать это следующим образом:

CancellationToken ct; 
ct.Register(() => 
{ 
    // this callback will be executed when token is cancelled 
    task_comletion_source.TrySetCanceled(); 
}); 

Обратите внимание, что он будет бросать исключение на вашем ЖДУТ, которые вы должны справиться.

+1

Оп понятый, вы можете захотеть «Аннулировать токен»; token.Register (... ', чтобы больше соответствовать его коду.Также вы можете попробовать' TrySetCanceled' (и использовать 'TrySetResult'), чтобы вы не выбрали исключение, если задача уже выполнена. –

+0

Спасибо, много, токен источник не проблема здесь, потому что он не был вовлечен (в оригинальном редактировании :-)). – astrowalker

+0

@ScottChamberlain, ах, спасибо за напоминание о 'Try ...', я случайно попал в эту ловушку, но теперь я не упаду благодаря вам. – astrowalker

3

Я рекомендую вам не строить это самостоятельно. Существует ряд крайних случаев вокруг маркеров отмены, которые являются утомительными для получения права. Например, если регистрация, возвращенная с Register, никогда не удаляется, вы можете получить утечку ресурсов.

Вместо этого, вы можете использовать метод Task.WaitAsync расширения от моего AsyncEx.Tasks library:

var task_completion_source = new TaskCompletionSource<bool>(); 
observable.Subscribe(b => 
{ 
    if (b) 
    task_completion_source.SetResult(true); 
}); 
await task_completion_source.Task.WaitAsync(cancellationToken); 

На стороне записки, я настоятельно рекомендую вам использовать ToTask вместо явного TaskCompletionSource. Опять же, ToTask отлично подходит для вас.

+0

Спасибо, но, конечно, мне любопытно - можете ли вы упомянуть крайние случаи использования 'TaskCompletionSource' (или указать на них)? До сих пор у меня не было проблем с его использованием. – astrowalker

+0

'Set *' будет бросать, если задача уже завершена. 'Set *' и 'TrySet *' по умолчанию разрешают синхронные продолжения (ваш callstack после 'await' может быть * внутри *' Подписаться'. Задача по умолчанию разрешает дочерние задачи. –

+0

@ StephenCleary, при каких обстоятельствах может возникнуть такой краевой случай (Регистрация не утилизирована)? –

Смежные вопросы