When using the Asynchronous Programming Model it is usually recommended to match every BeginXXX
with an EndXXX
, otherwise you risk leaking resources kept while the asynchronous operation is still "running".
Is that still the case if the class implements IDisposable
and Dispose
was called on the instance?
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 the EndXXX
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:
- Calling
EndXXX
after disposing the UDPClient
instance will result in it directly throwing an ObjectDisposedException
.
- No resources are disposed in or as a result of the
EndXXX
call.
- The resources tied up with this operation (native overlapped and pinned unmanaged buffers), will be recycled on the async operation completion callback.
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:
- The implementation could change in the future, breaking your assumptions.
- There are better ways to do this, using cancellation and time-outs for your async (I/O) operations (e.g. by calling
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:
The asynchronous BeginReceive
operation must be completed by calling the EndReceive
method. Typically, the method is invoked by the requestCallback delegate.
And the following for the underlying Socket.BeginReceive
method:
The asynchronous BeginReceive
operation must be completed by calling the EndReceive
method. Typically, the method is invoked by the callback delegate.
To cancel a pending BeginReceive
, call the Close
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:
public class UdpListener : IDisposable
{
private readonly IPAddress _hostIpAddress;
private readonly int _port;
private readonly Action<UdpReceiveResult> _processor;
private TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();
private CancellationTokenSource _tokenSource = new CancellationTokenSource();
private CancellationTokenRegistration _tokenReg;
private UdpClient _udpClient;
public UdpListener(IPAddress hostIpAddress, int port, Action<UdpReceiveResult> processor)
{
_hostIpAddress = hostIpAddress;
_port = port;
_processor = processor;
}
public Task ReceiveAsync()
{
// note: there is a race condition here in case of concurrent calls
if (_tokenSource != null && _udpClient == null)
{
try
{
_udpClient = new UdpClient();
_udpClient.Connect(_hostIpAddress, _port);
_tokenReg = _tokenSource.Token.Register(() => _udpClient.Close());
BeginReceive();
}
catch (Exception ex)
{
_tcs.SetException(ex);
throw;
}
}
return _tcs.Task;
}
public void Stop()
{
var cts = Interlocked.Exchange(ref _tokenSource, null);
if (cts != null)
{
cts.Cancel();
if (_tcs != null && _udpClient != null)
_tcs.Task.Wait();
_tokenReg.Dispose();
cts.Dispose();
}
}
public void Dispose()
{
Stop();
if (_udpClient != null)
{
((IDisposable)_udpClient).Dispose();
_udpClient = null;
}
GC.SuppressFinalize(this);
}
private void BeginReceive()
{
var iar = _udpClient.BeginReceive(HandleMessage, null);
if (iar.CompletedSynchronously)
HandleMessage(iar); // if "always" completed sync => stack overflow
}
private void HandleMessage(IAsyncResult iar)
{
try
{
IPEndPoint remoteEP = null;
Byte[] buffer = _udpClient.EndReceive(iar, ref remoteEP);
_processor(new UdpReceiveResult(buffer, remoteEP));
BeginReceive(); // do the next one
}
catch (ObjectDisposedException)
{
// we were canceled, i.e. completed normally
_tcs.SetResult(true);
}
catch (Exception ex)
{
// we failed.
_tcs.TrySetException(ex);
}
}
}
If the asynchronous operation represented by the IAsyncResult object has not completed when EndOperationName is called, EndOperationName blocks the calling thread until the asynchronous operation is complete.
That is an excerpt from the link you provided. Did you actually read it entirely? – BeckermanEndReceive
on the disposed_udpClient
instance it would simply throw anObjectDisposedException
without doing anything else. So no, you should not do that, although I am having a hard time imagining when/where you would callEndReceive
in such a case. – WheelockObjectDisposedException
. – SimplismFor each call to BeginOperationName, the application should also call EndOperationName to get the results of the operation.
Your application should always call EndOperationName for every BeginOperationName call. This guarantees that the operation described actually finished, otherwise it's anyone's guess as to whether or not it completed, or whether it had an error. (Omitting EndOperationName will also omit the tracking of errors, FYI.) – BeckermanEndXXX
on a disposed object. You also wouldn't know if the operation completed or not. All you'll ever get is anObjectDisposedException
. – SimplismThe point of Dispose is to free unmanaged resources.
– BeckermanEndReceive
, or as the result ofEndReceive
you might as well omit it. The native overlapped & GC pinned buffers will be freed on theIAsyncResult
completion callback anyway. So if you don't need to wait for the result in this specific case (and potentially others), no harm is done. – WheelockThe asynchronous BeginReceive operation must be completed by calling the EndReceive method.
You never actually let the operation complete, or give it a chance to. This will amost always have unintended side-effects. msdn.microsoft.com/en-us/library/… So, to answer your question, there is no point to calling EndOperationName on a disposed object, but whatever side-effects are there from disposing it before that call will likely remain. – BeckermanDispose
before letting the operation complete as it frees up any resources asDispose
is supposed to do. Moreover this is exactly what you are supposed to do when the operation can't complete. – SimplismIDisposable
, not aboutUdpClient
orSocket
. And I asked to get people's informed opinion. Too bad most people don't understand the question and don't bother trying to. And allClose
does onSocket
is callDispose
, which is obviously also true forUdpClient
andTcpClient
. – SimplismCancellationTokens
are for (msdn.microsoft.com/en-us/library/…). – BeckermanCancellationToken
is never used with the APM as APM predates it. It is however used withasync-await
butUdpClient.RecieveAsync
doesn't accept one. What you should do is useWithCancellation
, and since you usually do that inusing
scope you end up callingDispose
. Even if you don't the only way to clear these resources is callingDispose
. – SimplismClose
, not disposing the client/connection and omitting theEndReceive
call as you suggest they do. – WheelockClose
does onSocket
andUdpClient
(andTcpClient
for that matter) is callDispose
. Obviously there's no generalIClosable
interface and theIDisposable
in these classes is implemented explicitly. " CONSIDER providing methodClose()
, 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." – Simplism