Why does unity keep freezing?

This is getting really annoying since every time I exit play mode I’ll have to open task manager, force close unity, and reopen it. I have a rough idea on where and when this happens, but it happens on approx. 30kb worth of scripts, combined with the fact that it is related to asnyc/await operations and maybe even navmeshagents. I have made sure to call Dispose and ResetPath in OnDestroy but it still happens. This is still a bit vague so feel free to ask for more info.
Here are some snippets of code:

private void OnDestroy()
{
	repathingTimer.Dispose(); //is of type 'Timer', partially shown below
	teacherAgent.ResetPath();
	close = true; //tells some async function to stop its operation
}
	public sealed class Timer : IDisposable
	{
		public async Task Start()
		{
			//check for stuff

			while ((millisecondsElapsed < Interval && Status == TimerStatus.Ticking) || (millisecondsElapsed > 0 && Status == TimerStatus.Reversing))
			{
				//do stuff

				await Task.Yield();
				millisecondsElapsed += (int)(Time.deltaTime * 1000f * (Status == TimerStatus.Reversing ? -1f * reverseMultiplier : 1f));
				//invoke an event
			}

			//finalize some stuff
		}

		public void Dispose()
		{
			if (Status == TimerStatus.Disposed)
			{
				return;
			}
			//close timer then proceed

			Status = TimerStatus.Disposed;
			Interval = millisecondsElapsed = -1;

			//setting events to null

			GC.SuppressFinalize(this);
		}

		~Timer()
		{
			this.Dispose();
		}
	}
}
//example of where the 'close' variable is used
public async Task ObserveStrong(Transform objectToObserve, Timer timer)
{
    //prepare some stuff

	for ((Quaternion angle, float duration) locationToLookAt; timer.Status == Timer.TimerStatus.Reversing;)
	{
		if (close)
		{
			return;
		}
		//do some stuff
	}

	//finalize some stuff
}

That’s an awful lot of code to just make a timer. AND it’s manipulating something in GC …?!

Why not just a float and adjust it by Time.deltaTime ?

If your answer is “I need a very short timer,” there is just no need.

Besides, in your observer loop, if close is false, would just spin indefinitely, at least as far as I can tell. That’s a deadlock.

Unity will lock up 100% of the time EVERY millisecond your scripting code is running.

Nothing will render, no input will be processed, no Debug.Log() will come out, no GameObjects or transforms will appear to update.

Absolutely NOTHING will happen… until your code either:

  • returns from whatever function it is running

  • yields from whatever coroutine it is running

As long as your code is looping, Unity isn’t going to do even a single frame of change. Nothing.

No exceptions.

“Yield early, yield often, yield like your game depends on it… it does!” - Kurt Dekker

Here’s a handy tool that might help you track down unknown infinite loops:

Here’s how I do my game timers:

Cooldown timers, gun bullet intervals, shot spacing, rate of fire:

As previously mentioned, this is overly complex code for a timer. There are several key issues to address:

  • Adding Time.deltaTime repeatedly can lead to incorrect results for long-running timers due to floating-point precision errors and the explicit conversion to int on each iteration.
  • Finalizers have very niche use cases in C# and can create several issues due to the garbage collector, specifically:
    1. Memory release is delayed because the GC must track finalizers that haven’t run yet.
    2. Objects with finalizers are considered root objects, meaning they retain references to other objects, preventing those objects from being collected. This results in wasted memory.
    3. The programmer cannot predict when or how objects enter the finalization queue, making the order of finalizer execution unpredictable.
    4. A special case arises when a finalizer needs to wait for an event, potentially blocking other finalizers in the queue and wasting processing power and memory.
    5. Another reason to be cautious with finalizers is that they may run if an object throws an exception during construction. If the exception is caught, the object may not have been properly initialized, so a finalizer should never assume that the object’s fields have been initialized correctly.

In this case, at minimum there is double the job the GC has to do for each timer instance, when traversing the object graph and discovering objects eligible for garbage collection, because instead of releasing the object with the finalizer and all its direct and indirect references, it has to traverse that part again.

  • Using a variable like close is not the correct way to stop an asynchronous operation. Instead, use the CancellationTokenSource class.
  • Declaring an async method without an await expression, such as in ObserveStrong, serves no meaningful purpose.

The freezing part has something to do with how the finalizer is used and how the asynchronous part doesn’t stop when the play mode stops.

With the exception of the first issue, I believe most of these problems stem from how the asynchronous part with Task type is used here, which doesn’t integrate well with Unity’s game loop. Instead of await Task.Yield(), try using Unity’s Awaitable type. Awaitable.NextFrameAsync should achieve the desired behavior while respecting Unity’s game loop. This way, when you stop playing in the editor, you won’t need the extra workarounds this code currently implements to handle Task.

1 Like

Maybe you’re right, but my timer tries to invoke an event per frame, to do some stuff; so how can I find a much neater and better workaround?

Well then, I decided to remove it.

How would I go about using and/or implementing these two? Also, I use Task.Delay() quite often, is there also a replacement for it in Awaitable?

Having code that repeats in a loop and awaits Awaitable.NextFrameAsync, means that it runs once each frame, just invoke your event in that loop.

If you implement the second, you don’t need the first as the Awaitable doesn’t need a mechanism to be stopped when you exit play mode, it stops automatically.

If you need your code to execute asynchronously once each frame, use await Awaitable.NextFrameAsync() or await Awaitable.EndOfFrameAsync() just look at the examples at the documentation.

https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Awaitable.NextFrameAsync.html
https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Awaitable.EndOfFrameAsync.html

You can also get some ideas for timers from my blog post https://giannisakritidis.com/blog/Unity-Timers/ and the repo with the code: https://github.com/meredoth/Unity-Timers

It has different implementations with different complexity, some have more functionality some less and it also has one using the Awaitable just mix and match the implementation and the functionality depending on your needs.

1 Like

Unity allows you to customize the PlayerLoop if you truly need a “per-frame” update method.

However, in 100% of these cases you can architect your game around a single MonoBehaviour (make it singleton if you have to) that calls out to other systems either hardcoded or via events or via a baseclass or interface driven registration system (eg RegisterForMyUpdateEvents(IMyUpdateEventReceiver receiver)).

1 Like

Invoke once per frame? Do some stuff? WOW… that really would be an amazingly useful feature for Unity to implement within the engine itself!!

1 Like