Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
708 views
in Technique[技术] by (71.8m points)

c# - Cancel NetworkStream.ReadAsync using TcpListener

Consider the following simplified example (ready to roll in LinqPad, elevated account required):

void Main()
{
    Go();
    Thread.Sleep(100000);
}
async void Go()
{
    TcpListener listener = new TcpListener(IPAddress.Any, 6666);
    try
    {
        cts.Token.Register(() => Console.WriteLine("Token was canceled"));
        listener.Start();
        using(TcpClient client = await listener.AcceptTcpClientAsync()
                                               .ConfigureAwait(false))
        using(var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
        {
            var stream=client.GetStream();
            var buffer=new byte[64];
            try
            {
                var amtRead = await stream.ReadAsync(buffer,
                                                     0,
                                                     buffer.Length,
                                                     cts.Token);
                Console.WriteLine("finished");
            }
            catch(TaskCanceledException)
            {
                Console.WriteLine("boom");
            }
        }
    }
    finally
    {
        listener.Stop();
    }
}

If I connect a telnet client to localhost:6666 and sit around doing nothing for 5 seconds, why do I see "Token was canceled" but never see "boom" (or "finished")?

Will this NetworkStream not respect cancellation?

I can work around this with a combination of Task.Delay() and Task.WhenAny, but I'd prefer to get it working as expected.

Conversely, the following example of cancellation:

async void Go(CancellationToken ct)
{
    using(var cts=new CancellationTokenSource(TimeSpan.FromSeconds(5)))
    {
        try
        {
            await Task.Delay(TimeSpan.FromSeconds(10),cts.Token)
                                        .ConfigureAwait(false);
        }
        catch(TaskCanceledException)
        {
            Console.WriteLine("boom");
        }
    }
}

Prints "boom", as expected. What's going on?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

No, NetworkStream does not support cancellation.

Unfortunately, the underlying Win32 APIs do not always support per-operation cancellation. Traditionally, you could cancel all I/O for a particular handle, but the method to cancel a single I/O operation is fairly recent. Most of the .NET BCL was written against the XP API (or older), which did not include CancelIoEx.

Stream compounds this issue by "faking" support for cancellation (and asynchronous I/O, too) even if the implementation doesn't support it. The "fake" support for cancellation just checks the token immediately and then starts a regular asynchronous read that cannot be cancelled. That's what you're seeing happen with NetworkStream.

With sockets (and most Win32 types), the traditional approach is to close the handle if you want to abort communications. This causes all current operations (both reads and writes) to fail. Technically this is a violation of BCL thread safety as documented, but it does work.

cts.Token.Register(() => client.Close());
...
catch (ObjectDisposedException)

If, on the other hand, you want to detect a half-open scenario (where your side is reading but the other side has lost its connection), then the best solution is to periodically send data. I describe this more on my blog.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...