EDIT: So I re-read the doc at msdn, and it says "Any exception the delegate generates will be propagated out of this method call."
I'm not sure what it means by "propagated out of this method call", because even if I move the .Register() call into the try block the exception is still not caught.
What this means is that the caller of your cancellation callback (the code inside .NET Runtime) won't make an attempt to catch any exceptions you may throw there, so they will be propagated outside your callback, on whatever stack frame and synchronization context the callback was invoked on. This may crash the application, so you really should handle all non-fatal exceptions inside your callback. Think of it as of an event handler. After all, there may be multiple callbacks registered with ct.Register()
, and each might throw. Which exception should have been propagated then?
So, such exception will not be captured and propagated into the "client" side of the token (i.e., to the code which calls CancellationToken.ThrowIfCancellationRequested
).
Here's an alternative approach to throw TimeoutException
, if you need to differentiate between user cancellation (e.g., a "Stop" button) and a timeout:
public async Task<int> Read( byte[] buffer, int? size=null,
CancellationToken userToken)
{
size = size ?? buffer.Length;
using( var cts = CancellationTokenSource.CreateLinkedTokenSource(userToken))
{
cts.CancelAfter( 1000 );
try
{
var t = stream.ReadAsync( buffer, 0, size.Value, cts.Token );
try
{
await t;
}
catch (OperationCanceledException ex)
{
if (ex.CancellationToken == cts.Token)
throw new TimeoutException("read timeout", ex);
throw;
}
return t.Result;
}
catch( Exception ex )
{
Debug.WriteLine( "exception" );
return 0;
}
}
}