How to abort socket's BeginReceive()?

2019-01-17 14:29发布

问题:

Naturally, BeginReceive() will never end if there's no data. MSDN suggests that calling Close() would abort BeginReceive().

However, calling Close() on the socket also performs a Dispose() on it, as figured out in this great ansewr, and consequently EndReceive() would throw an exception because the object is already disposed (and it does!).

How should I proceed?

回答1:

It seems like this is by (the very dumb) design. You must have this exception thrown and caught in your code.

MSDN looks silent about it indeed, but if you look at the documentation of another asynchronous socket method, BeginConnect(), here's what we find:

To cancel a pending call to the BeginConnect() method, close the Socket. When the Close() method is called while an asynchronous operation is in progress, the callback provided to the BeginConnect() method is called. A subsequent call to the EndConnect(IAsyncResult) method will throw an ObjectDisposedException to indicate that the operation has been cancelled.

If it is the proper way of doing for BeginConnect, it is probably so for BeginReceive as well. This is certainly a poor design on the part of Microsoft's async API, because making the user necessarily throw and catch exception as a part of a normal flow would annoy the debugger. You have really no way to "wait" until the operation is completed, because Close() is what completes it in the first place.



回答2:

I am surprised no one recommended using SocketOptions.

Once the stack has the send or receive operation it is bound by the socket options of the socket.

Use a small send or receive timeout and use it before the operation so you don't care if it's changed during that same operation to something shorter or longer.

This will cause more context switching but will not require closing the socket under any protocol.

For example:

1) Set a small timeout

2) Perform operations

3) Set timeout larger

This is similar to using Blocking = false but with an automatic timeout that you specify.



回答3:

You can read my solution of this problem here(using comment of Pavel Radzivilovsky here): UdpClient.ReceiveAsync correct early termination



回答4:

For TCP socket connections, you can use the Connected property to determine the state of the socket before trying to access any disposed methods. Per MSDN:

"The Connected property gets the connection state of the Socket as of the last I/O operation. When it returns false, the Socket was either never connected, or is no longer connected."

Since it says "no longer connected" it implies that a Close() was previously called on the socket. If you check whether the socket is Connected at the start of the receive callback, there will be no exception.



回答5:

Another solution would be to send "yourself" a "control message" using a socket bound to a different port. It's not exactly an abort, but it would end your async operation.



回答6:

I was struggling with this as well but as far as I can tell using a simple boolean flag before calling .BeginReceive() will work as well (so there'll be no need for exception handling). Since I already had start/stop handling, this fix was a matter of one if statement (scroll down to the bottom of the OnReceive() method).

if (_running)
{
    _mainSocket.BeginReceive(_data, 0, _data.Length, SocketFlags.None, OnReceive, null);
}                

Should I have overlooked something with this approach, let me know!