How to abort socket's BeginReceive()?
Naturally, BeginReceive()
will never end if there's no data.
MS开发者_JAVA百科DN 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 answer, and consequently EndReceive()
would throw an exception because the object is already disposed (and it does!).
How should I proceed?
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.
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.
You can read my solution of this problem here(using comment of Pavel Radzivilovsky here): UdpClient.ReceiveAsync correct early termination
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.
In the ReceiveCallback I checked client.Connected within the try block. Now, when data is received after BeginReceive, I can call client.Close(); This way, I do not see exceptions. I send modbus-TCP requests every 200mS, and get responses in time. The console output looks clean. I used a windows forms app, to test this.
private static void ReceiveCallback(IAsyncResult ar)
{
try
{
// Retrieve the state object and the client socket
// from the asynchronous state object.
StateObject state = (StateObject)ar.AsyncState;
Socket client = state.workSocket;
if (client.Connected)
{
// Read data from the remote device.
state.dataSize = client.EndReceive(ar);
if (state.dataSize > 0)
{
Console.WriteLine("Received: " + state.dataSize.ToString() + " bytes from server");
// There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, state.dataSize));
// Get the rest of the data.
client.BeginReceive(state.buffer, 0, StateObject.BUFFER_SIZE, 0,
new AsyncCallback(ReceiveCallback), state);
state.dataSizeReceived = true; //received data size?
dataSize = state.dataSize;
buffer = state.buffer.ToArray();
dataSizeReceived = state.dataSizeReceived;
string hex = ByteArrayToString(state.buffer, state.dataSize);
Console.WriteLine("<- " + hex);
receiveDone.Set();
client.Close();
}
else
{
Console.WriteLine("All the data has arrived");
// All the data has arrived; put it in response.
if (state.sb.Length > 1)
{
Console.WriteLine("Length: " + state.sb.Length.ToString());
}
// Signal that all bytes have been received.
receiveDone.Set();
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
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.
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!
精彩评论