If you have a pending operation, eg
stream.BeginRead(_buffer, 0, _buffer.Length, _asyncCallbackRead, this);
and you close the stream provider, eg
serialPort.Close();
you unsurprisingly cause an exception.
Is there an preferred method by which one might cancel a pending APM operation, prior to closing the port?
Colby's reply is not the answer I hoped for, but he does at least close off a fruitless avenue in inquiry.
Happily I have found a solution.
For each stream I maintain various state information in a class DeviceSession
. This class has a method ReadStream
providing the implementation for the AsyncCallback
that handles incoming data.
Note that _asyncCallbackRead
and every other variable beginning with an underscore is a class private member assigned in the constructor of DeviceSession.
The constructor also provides the initial call to _stream.BeginRead
.
void ReadStream(IAsyncResult ar)
{
if (IsOpen)
try
{
DevicePacket packet;
int cbRead = _stream.EndRead(ar);
_endOfValidData += cbRead;
while ((packet = GetPacket()) != null)
CommandStrategy.Process(this, packet);
_stream.BeginRead(_buffer, _endOfValidData,
_buffer.Length - _endOfValidData,
_asyncCallbackRead, null);
}
catch (Exception ex)
{
Trace.TraceError("{0}\r\n{1}", ex.Message, ex.StackTrace);
_restart(_streamProvider, _deviceId);
}
}
Notice that I haven't bothered to set ar.AsyncState
. Because the callback delegate refers to the method of a specific instance of DeviceSession, detailed and strongly typed context information (contained in the members of this instance of DeviceSession) is automatically in scope. This is the point of having a session object.
Back on the subject of aborting a listener, closing the stream provider triggers the callback but attempting to invoke EndRead results in an IOException
.
Generally such an exception indicates a fault requiring a listener restart and one will want to respond by restarting the stream provider and recreating the session. This is complicated by the absence of a reliable stream-provider-independent way to determine whether the provider has faulted or the user is trying to restart the connection (eg plugged a new device into the port).
The trick is to add more context (IsOpen
) to the DeviceSession
to indicate whether the session is open or has been closed, and use it to smoothly complete the final abortive execution of ReadStream
.
If IsOpen
is true
then an IOException
represents a failure in need of recovery. If IsOpen
is false
the failure was deliberately induced and no action is required.