1

I'd like to make a HTTP request when an object is being collected by the Garbage Collector. I put a simple call in the finailzer of the class, which works fine as long as the application is not shutting down.

When the program is finished and my application wants to shut down, the GC calls the finalizer as before, but this time the request get stuck or just exits without an exception. At least the Studio doesn't show an exception, the program just terminates when the call is sent.

Unfortunately I must use the finalizer to send this request, so please do not suggest to use Dispose instead of the finalizer. Let's just find a way to do it from there if possible. :)

Here is the important part of my code:

class MyExample
{
    private readonly HttpClient myClient;

    public MyExample()
    {
        var handler = new HttpClientHandler();
        handler.UseProxy = false;
        handler.ServerCertificateCustomValidationCallback = (a, b, c, d) => true;

        this.myClient = new HttpClient(handler);
        this.myClient.BaseAddress = new Uri("https://wonderfulServerHere");
    }

    public async void SendImportantData() => await this.myClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "ImportantData"));

    ~MyExample()
    {
        this.SendImportantData();
    }
}

class Program
{
    static void Main(string[] args)
    {
        MyExample ex = new MyExample();

        /* ... */

        ex = new MyExample();

        /* ... */

        GC.Collect();
        GC.WaitForPendingFinalizers(); // Works fine here

       /* ... */
    } // Doesn't work here
}
  • How do you know it isn't working? The code exiting without an exception isn't proof that it isn't working. Are you meaning to do a POST and include some data rather than a GET? Is there a possibility that the finalizer just isn't being called for the object? – Luke Apr 10 '17 at 12:16
  • Good question. The request simply doesn't show up on the other side. And the real code uses POST, which makes no difference form this example with GET. – Attila Szobonya Apr 10 '17 at 12:28
  • 1
    You just cannot do that when runtime is terminating. But I'm curious what situation might lead to a requirement to make web request in finalizer? – Evk Apr 10 '17 at 12:49
  • `Let's just find a way to do it from there if possible.` It's not possible, sorry. See ["Impossibility of Shutdown Logic" in this old article of mine](https://www.codeproject.com/kb/dotnet/idisposable.aspx). – Stephen Cleary Apr 10 '17 at 14:04
  • Thanks for your input, especially for the interesting reading. Desire for fool-proof operation wanted to make me doing that, sorry that I won't go into details. :) Thank you for your help. – Attila Szobonya Apr 11 '17 at 12:40

2 Answers2

2

You are hitting a wall here. Finalizers are not guaranteed to be executed under all conditions:

Are .net finalizers always executed?

A finalizer may not run, for example, if:

Another finalizer throws an exception.
Another finalizer takes more than 2 seconds.
All finalizers together take more than 40 seconds.
An AppDomain crashes or is unloaded (though you can circumvent this with a critical finalizer (CriticalFinalizerObject, SafeHandle or something like that)
No garbage collection occurs
The process crashes

That is why it is not recommended to use finalizers besides few cases it is designed for: https://csharp.2000things.com/tag/finalizer/

Implement a finalizer only when the object has unmanaged resources to clean up (e.g. file handles)
Do not implement a finalizer if you don’t have unmanaged resources to clean up
The finalizer should release all of the object’s unmanaged resources
Implement the finalizer as part of the dispose pattern, which allows for deterministic destruction
The finalizer should only concern itself with cleanup of objects owned within the class where it is defined
The finalizer should avoid side-effects and only include cleanup code
The finalizer should not add references to any objects, including a reference to the finalizer’s own object
The finalizer should not call methods in any other objects
Community
  • 1
  • 1
Bobo
  • 335
  • 2
  • 10
0

Have you tried ex = null; before GC.Collect();

Placing an HTTP request, and in general doing anything non-trivial, from within a finalizer, is outrageously ill-conceived. Expecting that to work even as your application is shutting down, is beyond ill-conceived. At that time, part of the stack responsible for delivering your HTTP request may have already been garbage-collected. You have very few chances of getting it to work. Your only hope of this ever working is during a call to GC.WaitForPendingFinalizers() before your Main() returns.

But still, you are trying to do way too complex stuff from within your finalizer. If you google around for the "mandatory disposal" pattern, you will find the recommendation that the only thing that a finalizer should ever do is to produce an error log entry about the fact that some programmer, somewhere, forgot to invoke Dispose().

If you insist on doing actual work upon finalization, I would recommend rewriting your destructor to add your "Important Data" to a queue, and letting some other object process this queue. Of course, this processing will all need to be done before the last } of Main(). Once you are past the last } of Main(), "there be dragons".

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142