When using the Asynchronous Programming Model it is usually recommended to match every BeginXXX
with an EndXXX
, otherwise you risk leaking resources until the asynchronous operation completes.
Is that still the case if the class implements IDisposable
and the instance was disposed by calling Dispose
?
If for example I use UdpClient.BeginReceive
in a UdpListener
:
class UdpListener : IDisposable
{
private bool _isDisposed;
private readonly IPAddress _hostIpAddress;
private readonly int _port;
private UdpClient _udpClient;
public UdpListener(IPAddress hostIpAddress, int port)
{
_hostIpAddress = hostIpAddress;
_port = port;
}
public void Start()
{
_udpClient.Connect(_hostIpAddress, _port);
_udpClient.BeginReceive(HandleMessage, null);
}
public void Dispose()
{
if (_isDisposed)
{
throw new ObjectDisposedException("UdpListener");
}
((IDisposable) _udpClient).Dispose();
_isDisposed = true;
}
private static void HandleMessage(IAsyncResult asyncResult)
{
// handle...
}
}
Do I still need to make sure UdpClient.EndReceive
is called on the disposed _udpClient
(which will just result in an ObjectDisposedException
)?
Edit:
It isn't uncommon to dispose a UdpClient
(and other IDisposable
s) before all asynchronous operations completed as a way to cancel or implement a timeout, especially over operations that will never complete. This is also what's recommended throughout this site.
Considering the facts that
Dispose
(which should be identical toClose
1) releases any unmanaged resources (the GC releases the managed ones) and methods throwObjectDisposedException
when called on a disposed instance2 it should be safe to not callEndXXX
.That behavior of course depends on the specific implementation but it should be safe and it is indeed the case in
UdpClient
,TcpClient
,Socket
and more...Since APM predates the TPL and the
CancelationToken
that came with it you usually can't useCancelationToken
to cancel these asynchronous operations. That's why you also can't pass aCancelationToken
on the equivalentasync-await
methods (e.g.UdpClient.RecieveAsync
) as they are just a wrapper over theBeginXXX
/EndXXX
methods with a call toTask.Factory.FromAsync
. Moreover timeouts (likeSocket.ReceiveTimeout
) usually only affect the synchronous options and not the asynchronous ones.The only way to cancel this type of operations is by disposing the instance itself3 which releases all resources and invokes all the waiting callbacks which in turn usually call
EndXXX
and get the appropriateObjectDisposedException
. This exception is usually raised from the very first line of these methods when the instance is disposed.From what we know about APM and
IDisposable
callingDispose
should be enough to clear out any hanging resources and adding a call toEndXXX
will just raise an unhelpfulObjectDisposedException
and nothing more. CallingEndXXX
may protect you where the developer didn't follow the guidelines (it may not, it depends on the faulty implementation) but not calling it would be safe in many if not all of .Net's implementations and should be safe in the rest."Consider providing method
Close()
, in addition to theDispose()
, if close is standard terminology in the area. When doing so, it is important that you make theClose
implementation identical toDispose
and consider implementing theIDisposable.Dispose
method explicitly""Do throw an
ObjectDisposedException
from any member that cannot be used after the object has been disposed of"."To cancel a pending call to the
BeginConnect
method, close theSocket
. When theClose
method is called while an asynchronous operation is in progress, the callback provided to theBeginConnect
method is called."This has nothing to do with a class implementing
IDisposable
or not.Unless you can be sure that the async completion will free up any resources tied up with the async operation initiated through
BeginXXX
, and no cleanup is performed in, or as a result of theEndXXX
call, you need to ensure that you match your calls. The only way to be certain of this, is to inspect the implementation of a specific async operation.For the
UdpClient
example you chose, it happens to be the case that:EndXXX
after disposing theUDPClient
instance will result in it directly throwing anObjectDisposedException
.EndXXX
call.So in this case it is perfectly safe, without leakage.
As a general approach
I don't believe this approach is correct as a general approach, because:
Close
on the_udpClient
instance to force an I/O failure).Also, I would not want to rely on me inspecting the entire call stack (and not making a mistake in doing so) to ensure that no resources will be leaked.
Recommended and documented approach
Please note the following from the documentation for the
UdpClient.BeginReceive
method:And the following for the underlying
Socket.BeginReceive
method:I.e. this is the "by design" documented behavior. You can argue whether the design is very good, but it is clear in what the expected approach to cancellation is, and the behavior that you can expect as the result of doing so.
For your specific example (updated to do something useful with the async result), and other situations similar to it, the following would be an implementation that follows the recommended approach: