Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

C# Question About Events

Discussion in 'Scripting' started by JasonBricco, Nov 22, 2014.

  1. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    I have some questions about events and specifically about memory leaks.

    What I learned is that when you subscribe some method to an event, i.e.

    ClassName.Event += EventHandler;

    You need to pair this with:

    ClassName.Event -= EventHandler;

    To free up those resources and prevent memory leaks.

    What about in the following situations?

    1. You're unsubscribing the method from the event only on program quit. In this case it seems to me like the OS will free the resources for you and it's a waste to unsubscribe, right?

    2. The event is subscribed to a GameObject that exists within a certain scene, but I unload the scene to return to, say, a main menu. In this case the object that held the event is now destroyed, but I didn't unsubscribe the methods that were listening to it. Will this lead to a memory leak, or will these event subscriptions simply be garbage collected?

    Just trying to understand how it works better.

    Thanks!
     
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    In case 1, yeah, it doesn't matter; when your app goes away all its resources are released anyway.

    Case 2 is trickier. An event handler is really like any other reference, so the question is, what happens to those references when the object is destroyed? They ought to be garbage-collected, but personally I never have a lot of faith in such things — at the very least, it's impossible to say when that might happen, and whether that might cause a stutter in your framerate later. So I always prefer to clean things up myself when possible.

    So in this case, you could probably use the OnDestroy method to clean up your event handler.
     
    Stoven, Magiichan and JasonBricco like this.
  3. djfunkey

    djfunkey

    Joined:
    Jul 16, 2012
    Posts:
    201
    @JoeStrout Is it then a good idea to run the Garbage collector after every event handler?
     
  4. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    point 2: loading another scene and not removing the event handler on objects been destroyed leaves the handler stranded in memory and you will start getting errors in your application .. trust me I've experienced it more than once in Unity to be able to say it will cause you problems. You need to remove all handlers before object destruction.
     
    Stoven and JasonBricco like this.
  5. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    I've been unsubscribing in OnDestroy so far, but I was just wondering if that was actually wasteful or needed. It seems that it's needed, so thanks for that!
     
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Forcing the garbage collector to run isn't generally recommended. I would just unsubscribe in OnDestroy, as @JasonBricco says.
     
  7. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,670
    Or subscribe and unsubscribe in OnEnable and OnDisable:
    Code (csharp):
    1. void OnEnable() {
    2.     // Try to remove first to make sure we don't add multiple times:
    3.     ClassName.MyEventHandler -= OnMyEvent;
    4.     ClassName.MyEventHandler += OnMyEvent;
    5. }
    6.  
    7. void OnDisable() {
    8.    ClassName.MyEventHandler -= OnMyEvent;
    9. }
    This way, it will stop handling events if you disable the script. OnDisable is always called when the object is destroyed. It's okay to try to remove an event handler that hasn't been added to an event; it will just fail silently.
     
    JoeStrout likes this.
  8. Korno

    Korno

    Joined:
    Oct 26, 2014
    Posts:
    518
    If you are sharing events between components (Mono behaviours) on the same game object it isn't actually necessary to remove the event. It is probably still a good idea though, just in case you ever change your design in the future. However, If you are sharing events between game objects and the consumer (receiver) will live longer than the sender then it is very important as the consumer game object cant be garbage collected as the sender will still have a reference to it.
     
  9. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Another thing you can look at is the WeakEvent pattern:
    http://msdn.microsoft.com/en-us/library/aa970850(v=vs.90).aspx

    You still have to be a bit careful though in your event handler because when you "Destroy" a game object it doesn't automatically become null.. it just gets marked as destroyed and will be nullified at the end of the frame, so you'll still need to check if your GameObject has been destroyed before you execute anything in the method that is subscribed.
     
  10. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    It's almost never a good idea to force the garbage collector to run. Have listen to this .NET Rocks episode for an explanation.
     
  11. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I've been always wondering why? The only explanation I came up with is that all the temporary objects, which are sill in use at the moment of forcing the gc to run, will be moved to the next generation and will live there much longer than it is needed. But since the current Unity's GC has only one generation for all the objects it's not the case for us.

    So, if there's a reason to run the collection, are there any reasons not to?

    Thanks
     
    Last edited: Nov 24, 2014
  12. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    I'm going to run some code when I get a chance to test this out but I think you're a bit off track. I believe the Mono garbage collector actually is generational. That may not be true on every platform. I'd be willing to be that on Standalone builds at the bare minimum it will be generational. If targeting Windows Universal, Windows Store or Windows Phone then it is for sure generational as it's not using Mono but instead using the actual .NET runtime.

    As for GC.Collect, calling that method actually does a collect on all generations, so no it doesn't just force things into the next generation. Now, one thing you have to be aware of is objects that use finalizers. It's rare that you're going to need a finalizer but there are cases where it is handy. For instance, if you're using dependency injection to inject objects that may (or may not) use unmanaged resources.

    As an example, we do a fair amount of development against Entity Framework using the Unit of Work pattern with our UnitOfWork class implementing an IUnitOfWork interface. It is being injected by an IoC container into MVC (or WebAPI) controllers so it's not feasible to wrap it in "using" blocks and dispose of it. Instead, we implement a finalizer on the UnitOfWork class(es) that takes care of disposing the unmanaged resources. The drawback to this is that when a GC happens, the finalizer is executed and instead of being purged, the object is moved to the next generation.

    So, what that means is that calling GC.Collect() may cause some objects to just move to the next generation. Instead you would need to do:
    Code (csharp):
    1.  
    2. GC.Collect();
    3. GC.WaitForPendingFinalizers();
    4. GC.Collect();
    5.  
    See the second collection to collect those items that has finalizers.

    Now, as to why this is not necessarily good... it is a blocking operation and it's not very efficient (and also performs memory compaction in addition to cleanup). So, it will actually take longer and freeze your UI and everything else longer than letting the GC run normally. In almost all instances if you're calling GC.Collect manually it means you have a poor design and should consider pooling or some other mechanism.

    However, there are times when it may be beneficial. This was very true in XNA and also can hold true in Unity. If you're pooling but allocating a little, eventually you'll trigger a GC. If you have a game with multiple levels or loading of different areas, there are times where there is no user interaction and it may be beneficial to force a collection there to avoid a collection happening during play. So, between levels... or if you're loading an area... or something where the user may not notice or care if the game temporarily freezes would be the ideal time to call GC.Collect if you're going to do it.
     
  13. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I think, it's not generational, because it says it's not:
    Code (csharp):
    1. Debug.Log(GC.MaxGeneration); // prints 0
    However, I should have thought about Windows Phone and others. Good point.

    I don't understand why it will take longer being called manually.
     
  14. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    It takes longer because of the memory compaction and not being run in am optimized manner.
     
  15. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Yeah, forgot that Unitys mono version isn't new enough to use the SGen GC so only windows store and windows phone will have generational at this time. Still, no need to trigger a manual collection unless you have specific time windows when you want to do it to avoid collections during gameplay.