Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

await UniTask.NextFrame(); causes garbage allocation #667

Unanswered
sothasil asked this question in Q&A
Discussion options

Hi!
In same cases calling
await UniTask.NextFrame();

causes a lot of CG, should I be caching this and reusing it somehow?

I know it's NextFrame, because changing it to basic Yield fixes the CG issue

Unity 2022360
Unitask 2.5.10

You must be logged in to vote

Replies: 1 comment

Comment options

If you’re seeing GC spikes around await UniTask.NextFrame(), the call itself isn’t the culprit — UniTask.NextFrame() returns a struct awaitable and is designed to be allocation-free. What usually shows up in the Profiler is (1) the async state machine allocation in Editor/Development builds, (2) boxing/Task wrappers when methods return Task instead of UniTask, (3) extra work from registering a CancellationToken, or (4) captured closures/linq around the await. In other words, the GC is incidental, not inherent to NextFrame().

Two quick fixes cover most cases: (a) prefer async UniTask/UniTask returns (avoid async Task in hot paths), and (b) in Editor set Code Optimization = Release (Status Bar → "Code Optimization"), or profile a non-Development Player — the state machine is a class in Debug but a struct in Release, which removes those Editor-only allocations.

About caching: don’t cache and reuse a UniTask from NextFrame() — like ValueTask, a UniTask instance must not be awaited more than once (it may be pooled and returned to the pool after the await). If you need to reuse the result of some operation, use .Preserve() or UniTask.Lazy(...), but that’s not appropriate for "wait one frame"; just call UniTask.NextFrame() each time.

Semantics note: NextFrame() is the "yield return null replacement" (guaranteed to continue on the next frame). If simply yielding to the next PlayerLoop tick works better for your code, UniTask.Yield(...) is also fine; pick the one that matches your timing needs (your observation that swapping to Yield reduces GC likely means you were tripping one of the Editor/Task/closure cases above).
Minimal patterns that avoid GC in practice (Release/IL2CPP or Editor with Code Optimization = Release):

Common GC gotchas to check quickly: your method returns Task (causes boxing); you pass a fresh CancellationToken each time (registering can allocate); you close over locals (lambda/linq around the await); you’re judging in Editor/Development where the compiler emits class state machines. Unity’s Awaitable shows a similar "token registration allocates" behavior; UniTask also needs to register with tokens, so avoid per-frame token creation and prefer a long-lived one (e.g., destroyCancellationToken) unless you truly need a short-lived scope.
Docs in case you want to double-check behavior: NextFrame()/Yield() API and timing, zero-alloc design, and the Editor "Allocation on Profiler" caveat.

If this helped, please mark as accepted 🙏 — happy to dig into a snippet or profiler capture if you can share a small repro.

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /