Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NetworkStream.ReadAsync with a cancellation token never cancels

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;             }         }     } 
like image 933
Softlion Avatar asked Sep 14 '12 09:09

Softlion


People also ask

How do you handle a cancellation token?

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.

What is cancellation token source?

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.

What is cancellation token .NET core?

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.


2 Answers

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;        }     } } 
like image 67
Softlion Avatar answered Sep 28 '22 03:09

Softlion


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.

like image 28
usr Avatar answered Sep 28 '22 04:09

usr