Async Finalizes #2229
Replies: 7 comments
-
Sounds like something that needs to start on CoreCLR as the C# language cannot influence how the GC behaves in this manner. This would also require support from all of the runtimes, which is unlikely given that .NET Framework is seemingly put to pasture. |
Beta Was this translation helpful? Give feedback.
-
So you recommend that i propose it instead on the CoreCLR? |
Beta Was this translation helpful? Give feedback.
-
I think the discussion would be better suited to that repo since it deals with the internals of the CLR, including garbage collection. |
Beta Was this translation helpful? Give feedback.
-
I'm confused. It seems to me that the main feature of this proposal is that async finalizers run on a dedicated thread that does not block GC. But as far as I can tell, regular finalizers already do that: they pretty much behave the way you propose async finalizers should behave. For example, consider this code: using System;
using System.Threading;
class Program
{
static void Main()
{
for (int i = 0; i < 5; i++)
{
new C();
_ = new byte[10 * 1024 * 1024];
Console.WriteLine("Before GC.");
GC.Collect();
Console.WriteLine($"After GC, {GC.GetTotalMemory(false) / 1024 / 1024} MB allocated.");
Thread.Sleep(100);
}
Console.ReadLine();
}
}
class C
{
~C()
{
Console.WriteLine("Starting finalizer.");
Thread.Sleep(200);
Console.WriteLine("Finished finalizer.");
}
} The output of this code is:
This shows that even very slow finalizers do not prevent GC from running and that non-finalizable objects are still collected on time (since |
Beta Was this translation helpful? Give feedback.
-
Partial yes. The Proposal is around delegating async finalizers to N GC Threads/Tasks for cleanup. I might rewrite this later to clarify this. But the NetCore team has already rejected this proposal ( with clarification ) as unbenificial and hinted that every application that has the problem of too many finalizers should be re-written to use IAsyncDisposable that will come up soon. |
Beta Was this translation helpful? Give feedback.
-
https://github.com/dotnet/coreclr/issues/22598#issuecomment-463707561 |
Beta Was this translation helpful? Give feedback.
-
Have to repeat: People should stop writing finalizers, since at least 15 years ago. It does not do what it is expected to do. Whenever you really really need a finalizer (which typically means you are doing native code integration), you should use |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Asynchronous Object Finalizer
Summary
A Possibility to declare a finalizes that runs in Parallel or Asynchronous to the Garbage Collector.
Motivation
There are pattern within .net and c# that takes usage of the finalizesto free memory or do some cleanup methods. So it is easy that you can Produce Deadlocks within a Garbage Collectors thread when collecting this finalizes. This ether Async or Parallel execution of finalizescould reduce the pressure that any object emits to the Garbage Collector.
If a object only contains a finalizer it will survive 1 gc call more then any other object as it will be marked as dead in the "marking phase" and then be put in a queue that will be executed next time the GC is executed. This has 2 obvious problems.
This should solve or reduce the Impact of the known problem:
https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/performance#issue-the-garbage-collector-does-not-reclaim-objects-fast-enough
Detailed design
A finalizes should be ether annotated with an Attribute or allow the usage of the
async
keyword to create such an (call it async finalizes). If the Garbage Collector detects such an async finalizes it should start an external thread that is dedicated toasync finalizes
. In this thread should run onlyasnyc finalizes
. This Thread should always be running and processingasync finalizes
even outside normal GC activity phases.Instead of promoting this object to the next generation it should be assigned to the new Threads Heap and detached from its former root. This will ensure that the object itself can be removed right after the execution of the
async finalizes
and it will be no longer available from its origin.After the object was cleaned up, it should be marked for the compacting phase and be cleaned up from one of the others GC Threads as part of its usual iterations.
Flow:
GC Thread
a. If Object Has Async Finalizer -> Flag 'AsyncFinalizer'
b. If Object Has Finalizer -> Flag 'Finalizer'
c. If Object Has No Finalizer -> Flag 'Dead'
Async GC Thead
Example:
A Program that fetches Images from a HighSpeed Camera. It revives 100 images per second in a resolution of 1024x1024 and 32 bit depth. That is a need of memory of at least 3.355.443.200 bytes per second. The Program is using a
ArrayPool
to reduce the need of reinstantiating the byte arrays of each image. It uses anImage
class that wraps Meta informations and abyte[]
. The Byte Array contains the raw image data.Each time a new
Image
class is created, the constructor of that class calls theArrayPool
and obtains an new bufferedbyte[]
.Image
also declares a finalizer that returns this byte[] back to theArrayPool
. To ensure data integrity this has to be done in a synchronized manner with aLock()
statement.This as following effect on the Garbage Collector:
Each time the GC performs a cleanup, it will mark the dead
Image
objects for finalization. In the next invoke of GC it will execute each finalizer accordingly before cleaning up every other object. Assume there are only 100 Images marked for cleanup. Even if those images are cleaned up in the Concurrent GC run, due to theLock
statement. Each Finalizer takes roughly 20ms for execution (Due to theLock
and the call ofArrayPool
). This are 2 seconds where the GC will not collect any other object. In the 2 seconds are already 200 other Images created but not even marked for cleanup.Another Example would be to call External Resources that might need time to respond. If the Application shut down and a finalizer must clean up unmanaged resources that locks the caller, the whole shutdown is delayed as this call must finish before executing other code. This is especial time critical.
With async finalizer the execution of this user code is scheduled outside of the GCs normal execution and will not stop it from collecting other objects that are not in need of a finalizer. So during the execution of Finalizers the memory should not increase as the GC can perform its more "basic" task.
Drawbacks
This adds a lot more Presseure on older devices when enabled as the amount of threads can increase and requires architectonic changes the the internals of the Garbage Collector for all .net languages.
This design can be avoided by redesigning the programs way of handling images. This requires some sort of user reference handling when producing and using the
Image
and returning the Images to its source in a User-Thread. But when working with external libs and and complex architecture this might be not possible as the point of Disposal is not known to ether compile or runtime. This is the case when for example when using WPF and removing the Parent of a control. The child does not know its parent is Detached and will be removed by the GC silently. If the Control needs additional cleanup it HAS to use the finalizer. In this case of 3rd Party interaction the async finalizer would provide a great way of heavy cleanup.Alternatives
Please see Drawbacks
Unresolved questions
Design meetings
Beta Was this translation helpful? Give feedback.
All reactions