Here the proof.
Any idea what is wrong in this code ?
[TestMethod] public void TestTest() { var tcp = new TcpClient() { ReceiveTimeout = 5000, SendTimeout = 20000 }; tcp.Connect(IPAddress.Parse("176.31.100.115"), 25); bool ok = Read(tcp.GetStream()).Wait(30000); Assert.IsTrue(ok); } async Task Read(NetworkStream stream) { using (var cancellationTokenSource = new CancellationTokenSource(5000)) { int receivedCount; try { var buffer = new byte[1000]; receivedCount = await stream.ReadAsync(buffer, 0, 1000, cancellationTokenSource.Token); } catch (TimeoutException e) { receivedCount = -1; } } }
The wait handle of the cancellation token will become signaled in response to a cancellation request, and the method can use the return value of the WaitAny method to determine whether it was the cancellation token that signaled. The operation can then just exit, or throw a OperationCanceledException, as appropriate.
A CancellationTokenSource object, which provides a cancellation token through its Token property and sends a cancellation message by calling its Cancel or CancelAfter method. A CancellationToken object, which indicates whether cancellation is requested.
So CancellationToken can be used to terminate a request execution at the server immediately once the request is aborted or orphan. Here we are going to see some sample code snippets about implementing a CancellationToken for Entity FrameworkCore, Dapper ORM, and HttpClient calls in Asp. NetCore MVC application.
I finally found a workaround. Combine the async call with a delay task (Task.Delay) using Task.WaitAny. When the delay elapses before the io task, close the stream. This will force the task to stop. You should handle the async exception on the io task correctly. And you should add a continuation task for both the delayed task and the io task.
It also work with tcp connections. Closing the connection in another thread (you could consider it is the delay task thread) forces all async tasks using/waiting for this connection to stop.
--EDIT--
Another cleaner solution suggested by @vtortola: use the cancellation token to register a call to stream.Close:
async ValueTask Read(NetworkStream stream, TimeSpan timeout = default) { if(timeout == default(TimeSpan)) timeout = TimeSpan.FromSeconds(5); using var cts = new CancellationTokenSource(timeout); //C# 8 syntax using(cts.Token.Register(() => stream.Close())) { int receivedCount; try { var buffer = new byte[30000]; receivedCount = await stream.ReadAsync(buffer, 0, 30000, tcs.Token).ConfigureAwait(false); } catch (TimeoutException) { receivedCount = -1; } } }
Cancellation is cooperative. NetworkStream.ReadAsync
must cooperate to be able to be cancelled. It is kind of hard for it to do that because that would potentially leave the stream in an undefined state. What bytes have already been read from the Windows TCP stack and what haven't? IO is not easily cancellable.
Reflector shows that NetworkStream
does not override ReadAsync
. This means that it will get the default behavior of Stream.ReadAsync
which just throws the token away. There is no generic way Stream operations can be cancelled so the BCL Stream
class does not even try (it cannot try - there is no way to do this).
You should set a timeout on the Socket
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With