4

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 IDisposables) 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.

Community
  • 1
  • 1
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 7
    You're (potentially) disposing of your client before it has actually completed the operation. You almost certainly don't want to be doing that. The code has *correctness* problems, not performance problems. – Servy May 13 '15 at 18:10
  • @Servy this is just an example, not actual code. Imagine a timeout over some network operation. – i3arnon May 13 '15 at 18:12
  • 1
    What Servy is trying to say is you don't want to use APM inside a using statement where you escape the block before the APM call has a chance to even start. The issue is using APM inside a short lived using, the client object in your case needs to have the same lifetime as the entire Begin/End, not just the Begin one. – Ron Beyer May 13 '15 at 18:15
  • @RonBeyer yes, I understand his comment. As I said, this isn't actual code. I changed the example to a more believable (and complicated) one. – i3arnon May 13 '15 at 18:25
  • The purpose of the **EndOperationName** method is not to free resources alone, but to also guarantee the **Async** method finishes. `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? – Der Kommissar May 13 '15 at 18:26
  • If you were to call `EndReceive` on the disposed `_udpClient` instance it would simply throw an `ObjectDisposedException` without doing anything else. So no, you should not do that, although I am having a hard time imagining when/where you would call `EndReceive` in such a case. – Alex May 13 '15 at 18:29
  • @EBrown I did. Did you? This excerpt has nothing to do with the question. Calling EndXXX won't block the thread if the instance is disposed. It will get an `ObjectDisposedException`. – i3arnon May 13 '15 at 18:30
  • That excerpt has *everything* to do with the question. Another excerpt, from the document you *claim* to understand: `For 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.) – Der Kommissar May 13 '15 at 18:34
  • @EBrown you won't get any error, or any result out of calling `EndXXX` on a disposed object. You also wouldn't know if the operation completed or not. All you'll ever get is an `ObjectDisposedException`. – i3arnon May 13 '15 at 18:37
  • That's why you call it **before it's disposed.** – Der Kommissar May 13 '15 at 18:38
  • @PrestonGuillot [Timeout (or cancellation)](http://stackoverflow.com/a/11191070/885318) – i3arnon May 13 '15 at 18:40
  • @PrestonGuillot Indeed. The purpose of marking something IDisposable is not to get out of calling an **EndOperationName** method. (http://stackoverflow.com/questions/538060/proper-use-of-the-idisposable-interface) `The point of Dispose is to free unmanaged resources.` – Der Kommissar May 13 '15 at 18:40
  • In your concrete example, where no resources are disposed in the call to `EndReceive`, or as the result of `EndReceive` you might as well omit it. The native overlapped & GC pinned buffers will be freed on the `IAsyncResult` 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. – Alex May 13 '15 at 18:42
  • @EBrown If you call it before your thread **will** block until the operation completes, which may never come. Also, the question clearly states that the object is disposed. – i3arnon May 13 '15 at 18:42
  • If you were to read the API regarding the object you are using, you would see the following: `The 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. https://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.beginreceive(v=vs.110).aspx 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. – Der Kommissar May 13 '15 at 18:49
  • @EBrown If you were to read [**the code**](http://referencesource.microsoft.com/#System/net/System/Net/Sockets/UDPClient.cs,70336c67f1265216) you would see that it's perfectly safe to call `Dispose` before letting the operation complete as it frees up any resources as `Dispose` is supposed to do. Moreover this is [**exactly** what you are supposed to do](http://stackoverflow.com/a/12642359/885318) when the operation can't complete. – i3arnon May 13 '15 at 18:55
  • Then why did you ask the question of you already had an answer? (Even though that answer does not specify what you imply it does.) That answer does not even mention disposing an object, but instead says to close the socket. (Which is implicitly done by disposing it, but not the scope of the question.) Disposing and closing the socket are not the same thing. (They may be behind the scenes, but should you really rely on that?) – Der Kommissar May 13 '15 at 19:01
  • @EBrown I asked because there wasn't a question, and I would have answered if no one did. I asked a general question about `IDisposable`, not about `UdpClient` or `Socket`. 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 all [`Close` does on `Socket` is call `Dispose`](http://referencesource.microsoft.com/#System/net/System/Net/Sockets/Socket.cs,c84007a498b38b5f), which is obviously also true for `UdpClient` and `TcpClient`. – i3arnon May 13 '15 at 19:15
  • It's too bad that you want to rewrite the Asynchronous Programming Model for your situation. This is what `CancellationTokens` are for (https://msdn.microsoft.com/en-us/library/system.threading.cancellationtoken%28v=vs.110%29.aspx). – Der Kommissar May 13 '15 at 19:19
  • @EBrown `CancellationToken` is **never** used with the APM as APM predates it. It is however used with `async-await` but `UdpClient.RecieveAsync` doesn't accept one. What you should do is use `WithCancellation`, and since you usually do that in `using` scope you end up calling `Dispose`. Even if you don't the only way to clear these resources is calling `Dispose`. – i3arnon May 13 '15 at 19:38
  • @i3arnon Please note that from your referenced "throughout this site" recommendations, [this](http://stackoverflow.com/a/5979692/2573395) and [this](http://stackoverflow.com/questions/18309974/how-do-you-cancel-a-udpclientbeginreceive/18314614#18314614) and [this](http://stackoverflow.com/questions/12638104/await-udpclient-receiveasync-with-timeout/12642359#12642359), i.e. **all** of them, except your own referenced answer, recommend calling `Close`, **not** disposing the client/connection and omitting the `EndReceive` call as you suggest they do. – Alex May 13 '15 at 20:53
  • @Alex All `Close` does on `Socket` and `UdpClient` (and `TcpClient` for that matter) is call `Dispose`. Obviously there's no general `IClosable` interface and the `IDisposable` in these classes is implemented explicitly. [*" **CONSIDER** providing method `Close()`, in addition to the `Dispose()`, if close is standard terminology in the area. When doing so, it is important that you make the `Close` implementation identical to `Dispose` and consider implementing the `IDisposable.Dispose` method explicitly."*](https://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx) – i3arnon May 13 '15 at 22:20

2 Answers2

4

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:

  1. Calling EndXXX after disposing the UDPClient instance will result in it directly throwing an ObjectDisposedException.
  2. No resources are disposed in or as a result of the EndXXX call.
  3. 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:

  1. The implementation could change in the future, breaking your assumptions.
  2. 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); 
        }
    }
}
Alex
  • 13,024
  • 33
  • 62
  • 1. How is *"being sure that the async completion will free up any resources"* unrelated to `IDisposable` if `IDisposable` is meant exactly for freeing up resources and disposed objects throw `ObjectDisposedException` for instance methods (like `EndXXX`) – i3arnon May 13 '15 at 19:31
  • 2. The resources in `UdpClient` tied with the async operation are indeed freed when the operation completes and the callback is called. The point is that these callbacks are also called on `Dispose`. That's the case for `UdpClient`, `TcpClient`, `Socket`, etc. That's exactly how `Dispose` frees resources. – i3arnon May 13 '15 at 19:32
  • 3. You suggest using cancellation or timeouts with `Close` as a better way, which is surprising as all `Close` does is (on `UdpClient`, `TcpClient`, `Socket`, etc.) is call `Dispose`. Your suggestion is exactly what the question suggests only it means getting an `ObjectDisposedException`. – i3arnon May 13 '15 at 19:32
  • @i3arnon you are relying on the results of inspection of the current code implementation, instead of the published documentation for these classes. The documentation is explicit about how one should cancel these operations and what the expected behavior is when you do. – Alex May 13 '15 at 20:28
  • Well, I do think the implementation speaks louder but nevermind. The documentation **is not at all** explicit about how you should cancel these operations (or what the outcome would be). It is however implicit about the fact that these operations are **uncancellable**. **The only way** you can cancel these operations (e.g. the async version of `Receive` on `UdpClient`) is to call `Dispose` on the instance. – i3arnon May 13 '15 at 22:21
  • @i3arnon Then you may want to explain how these are not explicit: "The asynchronous `BeginReceive` operation must be completed by calling the `EndReceive`" ,"To cancel a pending `BeginReceive`, call the `Close` method.", ["To cancel a pending call to the `BeginConnect()` method, close the `Socket`. ... A subsequent call to the `EndConnect(IAsyncResult)` method will throw an `ObjectDisposedException` to indicate that the operation has been cancelled"](https://msdn.microsoft.com/en-us/library/tad07yt6(v=vs.110).aspx) – Alex May 13 '15 at 22:39
  • So you agree with me now? That there's no way to cancel these operations other than disposing the instance itself? – i3arnon May 13 '15 at 22:46
  • @i3arnon No. It appears you prefer reading what you want to read instead of what it actually says: "call `Close`" and "must be completed by calling `EndReceive`" is **not** the same as "call `Dispose` and do not call `EndReceive`". – Alex May 13 '15 at 22:50
  • I would say that you are the one reading what you want as *"A subsequent call to the `EndConnect` method will throw an `ObjectDisposedException` to indicate that the operation has been cancelled"* means that is what **will happen** and not what **must**. Calling `Socket.EndConnect` on a disposed socket will do **nothing other than raise the exception**. It would be equivalent to `throw new ObjectDisposedException("Socket");`. – i3arnon May 13 '15 at 22:54
  • @i3arnon: The proper solution would probably be for the documentation to explicitly specify whether or not `EndConnect` is required after `Dispose`, and also to indicate whether a `Dispose` that squeaks in between the completion of the connection and the call to `EndConnect` will cause the latter to throw an exception, or if the exception will only be thrown if the `Dispose` prevents the expected action from completing. It may be reasonable to require clients to call `EndConnect`, or for require that implementations not require anything beyond `Dispose`, but is either requirement stated? – supercat May 15 '15 at 22:10
  • @supercat I haven't found any explicit requirement on what to do in this exact situation. However, each implementation I actually look at show calling EndXXX will only result in an exception. – i3arnon May 15 '15 at 22:25
  • @i3arnon: What happens in scenarios where the action is completed prior to `Dispose`, and `EndXX` is called right around the time of `Dispose` [quite possibly in a different thread]? Does `Dispose` kill the evidence of the action's having completed? – supercat May 15 '15 at 22:37
  • @supercat If the call to `Dispose` already completed you get `ObjectDisposedException`. If it started but haven't completed yet there may be a scenario in which you get the result but it's more likely you get a `NullReferenceException` on some internal member that was already disposed/cleared/closed. – i3arnon May 15 '15 at 22:43
  • @i3arnon: A properly designed class should guarantee that either the `Dispose` will beat the completion or not, and should make it possible to discover which occurred. If `EndXXX` is expected to wait for an action to complete (as is often the case), then it should be *expected* that `Dispose` is going to happen on another thread [it *can't* happen on the thread which is blocking in the EndXXX call!], and thus properly-written code must include enough thread-safety logic to guard against that case even if nothing else is thread-safe. – supercat May 15 '15 at 23:13
  • @supercat It *is* expected that `Dispose` will be called from another thread and it is possible to discover whether the completion happened before the instance was disposed (in which you'll get an actual result back) or whether it didn't and you'll get an exception. – i3arnon May 15 '15 at 23:36
  • @supercat The point of this issue is when you already decided to dispose the instance and the disposal was complete there's no point in calling `EndXXX` as it's too late to get something other than an exception. – i3arnon May 15 '15 at 23:36
  • @i3arnon: By "the completion happened" do you mean "the EndXX call finished" or "the action finished even if the EndXX hadn't been performed"? If the action completes before `Dispose`, I would think that it would be reasonable for `EndXX` to relate the results of the successfully-performed exception; even if present implementations throw an exception in that case, I wouldn't think they should be contractually required to do so. To my mind, the purpose of `ObjectDisposed` exception should be to say "Disposal of the object has made it impossible to satisfy the contract for this action." – supercat May 18 '15 at 15:31
  • If the contract for the action can be completed despite the object's having been disposed, I would think that in many cases that would be preferable to throwing an exception, though there are some interesting corner cases, somewhat analogous to timeouts. There are three ways a blocking "read 1024" bytes request might sensibly be handled: (1) Either return 1024 bytes or throw an exception; (2) Wait until some data is available, and immediately return up to 1024 bytes of it; (3) Return whatever data arrive before timeout. If code is doing the first type of read and the object is disposed... – supercat May 18 '15 at 15:40
  • ...after 700 bytes were received, the method should discard the data and throw an exception. If it's doing one of the other types of read, however, it may make more sense to regard the `Dispose` as triggering an immediate timeout, in which case `Dispose` would cause the second style of read to throw an exception if no data was pending or else return normally with that pending data. In the third scenario, if a timeout with no data would cause a successful 0-bytes return, it may make sense to say that the first such read after `Dispose` would yield 0 bytes, and later ones throw an exception. – supercat May 18 '15 at 15:44
  • @supercat IIUC you're saying that an exception should be raised after Dispose only if it must. This isn't the case with these classes (and every other one that I checked) but more to the point I think that if you already called Dispose you have decided to let go of that operation, and while you may still call EndXXX and figure out if a result is there it's not worth it as in most (or all) cases you'll get back an exception which hurts performance. – i3arnon May 23 '15 at 00:46
  • @i3arnon: There are many cases where an out-of-thread "Dispose" on a service means "start shutting yourself down gracefully". Transactions which are being performed when a dispose occurs should either be rolled back or completed, and disposal should not prevent completed transactions from being reported as such. If one views the meaning of `Dispose` as "Consider yourself notified that your owner is no longer interested in having you perform your job", and ownership of an `IDisposable` or other resource as an obligation to notify it when you're no longer interested in it, then... – supercat May 23 '15 at 16:49
  • ...calling `Dispose` on a service while transactions are in progress would basically say that the *owner* isn't interested in having the object accept any more work, but entities from whom the service has accepted work would still have an interest until such time as they are notified that their jobs have either been completed or rolled back. – supercat May 23 '15 at 16:52
  • @supercat That isn't really the meaning of `IDisposable`. Its meaning is to have unmanaged resources waiting for Dispose to be released. APM waits for EndXXX to release resources. What I am saying is that when you combine the two you don't *need* EndXXX after Dispose. Can you still call it and maybe get a result? Yes. Extremely unlikey, but possible. But even then it's still safe not to call it as you already timed out. – i3arnon May 23 '15 at 17:33
0

Considering the facts that Dispose (which should be identical to Close1) releases any unmanaged resources (the GC releases the managed ones) and methods throw ObjectDisposedException when called on a disposed instance2 it should be safe to not call EndXXX.

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 use CancelationToken to cancel these asynchronous operations. That's why you also can't pass a CancelationToken on the equivalent async-await methods (e.g. UdpClient.RecieveAsync) as they are just a wrapper over the BeginXXX/EndXXX methods with a call to Task.Factory.FromAsync. Moreover timeouts (like Socket.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 appropriate ObjectDisposedException. 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 calling Dispose should be enough to clear out any hanging resources and adding a call to EndXXX will just raise an unhelpful ObjectDisposedException and nothing more. Calling EndXXX 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.


  1. "Consider providing method Close(), in addition to the Dispose(), if close is standard terminology in the area. When doing so, it is important that you make the Close implementation identical to Dispose and consider implementing the IDisposable.Dispose method explicitly"

  2. "Do throw an ObjectDisposedException from any member that cannot be used after the object has been disposed of".

  3. "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."

i3arnon
  • 113,022
  • 33
  • 324
  • 344