2014-08-29 7 views
1
Утилизация

надуманный пример, но предположим, что у меня есть следующие в методе асинхронном:CancellationTokenRegistrations

var cts = new CancellationTokenSource(); 
cts.CancelAfter(2000); 
cts.Token.Register(Callback); 
SomethingThatMightThrow(); 
await Task.Delay(10000, cts.Token); 

Это работает, как ожидалось, поскольку через пару секунд обратный вызов называется. Тем не менее, я хочу, чтобы избавиться регистрации после Task.Delay, поэтому предположим, что я сделать следующее изменение:

var cts = new CancellationTokenSource(); 
cts.CancelAfter(2000); 
using (cts.Token.Register(Callback)) 
{ 
    SomethingThatMightThrow(); 
    await Task.Delay(10000, cts.Token); 
} 

В этом случае обратный вызов не называется, по-видимому, потому что исключение из await Task.Delay... вызывает регистрацию быть перед тем, как он будет вызван.

Каков наилучший способ гарантировать, что Callback будет вызван на отмену и что регистрация всегда удаляется?

Я думаю, что в следующем, но я не уверен, насколько надежным является:

var cts = new CancellationTokenSource(); 
cts.CancelAfter(2000); 

var ctr = cts.Token.Register(Callback); 
try 
{ 
    SomethingThatMightThrow(); 
    await Task.Delay(10000, cts.Token); 
} 
finally 
{ 
    if (!cts.Token.IsCancellationRequested) 
     ctr.Dispose(); 
} 
+1

Не ясно ли вы * всегда * хотите/ожидать 'Callback' называться или, если есть какой-то момент в коде, после чего вы больше не хотите обратного вызова для вызова, даже если время CancelAfter. Если вы всегда хотите, чтобы Callback вызывался (даже если остальная часть вашего кода завершена), просто удалите 'CancellationTokenRegistration' в' Callback' –

+1

. Связанный: http://stackoverflow.com/questions/21367695/why- cancelationtokenregistration-exists-and-why-do-it-implement-idisposable –

+0

@MattSmith - я хотел, чтобы «Обратный звонок» вызывался только в том случае, если во время 'SomethingThatMightThrow() запрашивается отмена; ожидание Task.Delay (10000, cts.Token); 'block. – Duncan

ответ

2

CancellationToken.Register обычно используется для Interop новой системы CancellationToken со старой системой, которая использует какой-то другой вид уведомления для отмены. Он не предназначен для использования в качестве универсального «обратного вызова отмены».

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

using (var cts = new CancellationTokenSource()) 
{ 
    cts.CancelAfter(2000); 
    SomethingThatMightThrow(); 
    try 
    { 
    await Task.Delay(10000, cts.Token); 
    } 
    catch (OperationCanceledException) 
    { 
    Callback(); 
    } 
} 
+0

Спасибо, это полезно. Однако моя озабоченность этим подходом состояла в том, что я теперь полагаюсь на метод, который я ожидаю в блоке try, который своевременно бросает OperationCanceledException («Задача.Delay' ведет себя хорошо, я уверен, но в моем фактическом коде я бы ожидал чего-то еще), я думал, что, зарегистрировав обратный вызов на токене, я бы гарантировал, что он будет вызван очень скоро после отмена запрос. – Duncan

+0

@ Duncan: Если метод принимает «CancellationToken», он должен выдать «OperationCanceledException». –

+0

Это не так, что метод не может вызывать исключение, поскольку он может не бросать его так быстро, как хотелось бы. Я заметил, что некоторые из асинхронных методов Azure SDK, например, могут занять довольно много времени, чтобы ответить на отмену, сигнализируемую на токене ... – Duncan

0

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

Согласно комментарию, вы можете создать свой собственный таймер чтобы явно указать, когда метод «слишком долго запускается» в соответствии с вашими стандартами. Для этого вы можете использовать Task.WhenAny:

using (var cts = new CancellationTokenSource()) 
{ 
    try 
    { 
     var cancellationDelayTask = Task.Delay(2000, cts.Token); 
     var taskThatMightThrow = SomethingThatMightThrowAsync(cts.Token); 

     if ((await Task.WhenAny(taskThatMightThrow, cancellationDelayTask)) 
      == cancellationDelayTask) 
     { 
      // Task.Delay "timeout" finished first. 
     } 
    } 
    catch (OperationCanceledException) 
    { 
     Callback(); 
    } 
}