0

I have a pending ReadAsync operation on a TcpClient.

My application closes the TcpClient when it determines that no more communication is likely to occur. Calling TcpClient.Close on the socket causes Exception thrown: 'System.ObjectDisposedException' in mscorlib.dll to be thrown by the prior call to ReadAsync().

How can this exception be avoided?

Minimal example: (use putty, nc, etc to open a connection to test this)

using System;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using System.Threading.Tasks;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private TcpClient client;

        // Initialize the form
        public Form1()
        {
            InitializeComponent();
        }

        // Start the Listen() task when the form loads
        private void Form1_Load(object sender, EventArgs e)
        {
            var task = Listen();
        }

        // Listen for the connection
        public async Task Listen()
        {
            // Start the listener
            var listener = new TcpListener(IPAddress.Any, 80);
            listener.Start();

            // Accept a connection
            client = await listener.AcceptTcpClientAsync();

            // Wait for some data (the client doesn't send any for the sake of reproducing the issue)
            // An exception is generated in 'ReadAsync' after Button1 is clicked
            byte[] buffer = new byte[100];
            await client.GetStream().ReadAsync(buffer, 0, 100);
        }

        // I will click this button before the above call to `ReadAsync` receives any data
        private void button1_Click_1(object sender, EventArgs e)
        {
            // This causes an exception in the above call to `ReadAsync`
            client.Close();
        }
    }
}

CancellationToken can't be used to cancel ReadAsync(). Is there no way to close a connection with a pending ReadAsync() without throwing an exception? I don't want to use try/catch here unless absolutely necessary.

AndyMcoy
  • 179
  • 1
  • 3
  • 13

2 Answers2

0

Calling Socket.Shutdown() allows the connection to close gracefully causing ReadAsync() to return 0 as desired.

The socket object can then be disposed with a call to TcpClient.Close() after ReadAsync() returns 0.

// Listen for the connection
public async Task Listen()
{
    // ...

    // Wait for some data (the client won't be sending any at this point)
    byte[] msgBuf = new byte[100];
    var lengthRead = await client.GetStream().ReadAsync(msgBuf, 0, 100);
    if (lengthRead == 0)
    {
        // A graceful shutdown has occurred. The socket can now be disposed
        // by "TcpClient.Close()"
        client.Close();
    }
}

// I will click this button to close the connection before the above call to `ReadAsync` receives any data
private void button1_Click_1(object sender, EventArgs e)
{
    // This causes ReadAsync() to return 0 after which the socket can be disposed
    client.Client.Shutdown(SocketShutdown.Both);
}
AndyMcoy
  • 179
  • 1
  • 3
  • 13
-1

I have implemented a application that listens on a TCP port using TCPClient, instead of using the Task Listen() function why not use a while loop to keep the connection open, then you can use some condition tin your while loop to close the connection when your requirements are met...

  • I've chosen the async/await pattern to avoid having to run blocking code on a separate non-ui thread and having to deal with the complexity of multithreading. – AndyMcoy Dec 08 '19 at 17:19