c# - run - ¿Por qué existe CancelTokenRegistration y por qué implementa IDisposable?
cancellationtoken timeout (2)
He estado viendo un código que utiliza Cancellation.Register
con una cláusula de using
en el resultado de CancellationTokenRegistration
Registro:
using (CancellationTokenRegistration ctr = token.Register(() => wc.CancelAsync()))
{
await wc.DownloadStringAsync(new Uri("http://www.hamster.com"));
}
Entiendo que debe asegurarse de que IDisposable
un IDisposable
, pero ¿por qué incluso implementa IDisposable
? ¿Qué recursos tiene que liberar? Los únicos métodos que tiene tienen en cuenta la igualdad.
¿Qué pasa si no lo Dispose
? que fugas
¿Por qué incluso implementa IDisposable? ¿Qué recursos tiene que liberar?
IDisposable
se usa a menudo para cosas que no están relacionadas con la liberación de recursos; es una forma conveniente de garantizar que se hará algo al final de un bloque de using
, incluso si se produce una excepción. Algunas personas (no yo) argumentan que hacer esto es un abuso del patrón de Dispose
.
En el caso de CancellationToken.Register
, el "algo" es la CancellationToken.Register
del registro de la devolución de llamada.
Tenga en cuenta que en el código que publicó en su pregunta, el uso de un bloque de using
en el registro de cancelación cancelado es casi seguro que es un error: dado que wc.DownloadStringAsync
regresa inmediatamente, la devolución de llamada se cancelará inmediatamente, antes de que la operación tenga la oportunidad de cancelarse. por lo wc.CancelAsync
, nunca se llamará a wc.CancelAsync
, incluso si se señala el CancellationToken
.Tendría sentido si se (EDIT: ya no es cierto desde que se editó la pregunta) wc.DownloadStringAsync
la llamada a wc.DownloadStringAsync
, porque en ese caso no se alcanzaría el final del bloque de using
antes de completar wc.DownloadStringAsync
.
¿Qué pasa si no lo desechas? que fugas
En este caso, lo que sucede es que la devolución de llamada no está desregistrada. Es probable que realmente no importe, ya que solo se hace referencia al token de cancelación, y dado que CancellationToken
es un tipo de valor que normalmente se almacena solo en la pila, la referencia desaparecerá cuando quede fuera del alcance.Por lo tanto, no filtrará nada en la mayoría de los casos, pero podría hacerlo si almacena el EDIT: en realidad, eso no es correcto; Vea la respuesta de Noseratio para una explicación. CancellationToken
algún lugar del montón.
Este patrón es una forma conveniente de asegurarse de que se llame automáticamente a CancellationTokenRegistration.Unregister()
. Stephen Toub lo usa a menudo en sus publicaciones de programación paralela con .NET , por ejemplo, here .
Entiendo que debe asegurarse de que dispone de un IDisposable, pero ¿por qué incluso implementa IDisposable? ¿Qué recursos tiene que liberar? Los únicos métodos que tiene tienen en cuenta la igualdad.
En mi opinión, la mejor respuesta a esto se puede encontrar en la publicación del Marco de cancelación de .NET 4 de Mike Liddell de Microsoft:
Cuando se registra una devolución de llamada en un
CancellationToken
, se captura elExecutionContext
del subproceso actual para que la devolución de llamada se ejecute con el mismo contexto de seguridad. La captura del contexto de sincronización del hilo actual es opcional, se puede solicitar a través de una sobrecarga dect.Register()
si es necesario. Las devoluciones de llamada normalmente se almacenan y luego se ejecutan cuando se solicita la cancelación, pero si se registra una devolución de llamada después de que se haya solicitado la cancelación, la devolución de llamada se ejecutará inmediatamente en el hilo actual, o medianteSend()
en elSynchronizationContext
actual, si corresponde.Cuando se registra una devolución de llamada en un
CancellationToken
, el objeto devuelto es un registroCancellationToken
CancellationTokenRegistration
. Este es un tipo de estructura de luz que esIDiposable
y la eliminación de este objeto de registro hace que la devolución de llamada se cancele. Se garantiza que después de que el métodoDispose()
haya regresado, la devolución de llamada registrada no se está ejecutando ni comenzará posteriormente. Una consecuencia de esto es queCancellationTokenRegistration.Dispose()
debe bloquear si la devolución de llamada se está ejecutando actualmente. Por lo tanto, todas las devoluciones de llamadas registradas deben ser rápidas y no bloquearse por ninguna duración significativa.
Otro documento relevante de Mike Liddell es "Uso del soporte de cancelación en .NET Framework 4" (UsingCancellationinNET4.pdf) .
Actualizado , esto es verificable aquí en la Fuente de Referencia .
También es importante tener en cuenta que la devolución de llamada de cancelación se registra con el CancellationTokenSource
, no con el de CancellationToken
. Por lo tanto, si CancellationTokenRegistration.Dispose()
no está correctamente en el ámbito, el registro permanecerá activo durante toda la vida útil del objeto principal CancellationTokenSource
. Esto puede provocar una devolución de llamada inesperada cuando finalice el alcance de la operación asíncrona, por ejemplo:
async Task TestAsync(WebClient wc, CancellationToken token)
{
token.Register(() => wc.CancelAsync());
await wc.DownloadStringAsync(new Uri("http://www.hamster.com"));
}
// CancellationTokenSource.Cancel() may still get called later,
// in which case wc.CancelAsync() will be invoked too
Por lo tanto, es importante tener en cuenta el CancellationTokenRegistration
Registro desechable con el using
(o llamar al CancellationTokenRegistration.Dispose()
explícitamente con try/finally
).