Open-source project
Cysharp/UniTask avatar
Cysharp/UniTask

UniTask: allocation free async/await that stays inside Unity's PlayerLoop

Provides an efficient allocation free async/await integration for Unity.

11,203 stars1,014 forksC#MIT

At a glance

What is it?
Cysharp's UniTask replaces Task<T> with a struct type built on a custom async method builder, so coroutines, AsyncOperations and web requests can all be awaited without the per-await allocation garbage collection costs.
Who is it for?
UniTask makes the most sense for projects that have already decided async/await is the right shape for their Unity code and are now paying for it in allocations and a limited set of awaitable operations. Coroutine-heavy codebases gain the most, because the WaitForSeconds, WaitUntil and NextFrame equivalents remove the most boilerplate.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 91 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A struct type where the BCL gives you a class

The one line summary is that UniTask provides an efficient allocation free async/await integration for Unity, and the mechanism for that claim sits in the first bullet of the feature list: struct based `UniTask<T>` and a custom AsyncMethodBuilder to achieve zero allocation.

That distinction matters more than it sounds. `System.Threading.Tasks.Task<T>` is a reference type, so every await in a chain of them allocates. In a garbage collected environment with a throughput oriented runtime such as .NET that is fine, but a Unity project has a per frame allocation budget and a collector that will spike once a frame's worth of garbage accumulates. Making the awaitable a struct means the state machine and the completed result live in stack space or in pooled storage rather than on the heap.

The shape of ordinary async code does not change much. You declare the method with an `async UniTask<T>` return type and the compiler routes it through the custom builder:

csharp
async UniTask<string> DemoAsync()
{
    var asset = await Resources.LoadAsync<TextAsset>("foo");
    await SceneManager.LoadSceneAsync("scene2");
    return (asset as TextAsset)?.text ?? throw new InvalidOperationException("Asset not found");
}

Note that last line. `UniTask<T>` is a struct, so it cannot carry nullable fields the way a class can, and the pattern for a missing result is a thrown `InvalidOperationException` rather than a null check. Small details like that show the type is built around value semantics throughout, and they are the sort of thing you notice when moving code back and forth between the two styles.

Awaiting Unity's own AsyncOperations without adapters

The second bullet is that all Unity AsyncOperations and Coroutines become awaitable. This is the part that decides whether the library earns its place in a real project, because it means you do not have to wrap a `Coroutine` in a runner, hand roll an `IEnumerator` to `Task` adapter, or maintain your own extension methods for each of the dozens of Unity async APIs.

`Resources.LoadAsync` and `SceneManager.LoadSceneAsync` are directly awaitable in the sample above, and so is `UnityWebRequest.SendWebRequest`. Each one gets two optional decorators on top. `.WithCancellation(token)` propagates a cancellation signal into the await, and `.ToUniTask(progress)` converts the operation and surfaces its progress as a callback, with `Progress.Create<T>` offered as a lightweight stand in for `IProgress<T>`.

csharp
var asset2 = await Resources.LoadAsync<TextAsset>("bar").WithCancellation(this.GetCancellationTokenOnDestroy());
var asset3 = await Resources.LoadAsync<TextAsset>("baz").ToUniTask(Progress.Create<float>(x => Debug.Log(x)));

The cancellation pattern is worth sitting with for a moment. `GetCancellationTokenOnDestroy()` ties the outstanding operation to the lifetime of a GameObject, so when the object is destroyed the await stops instead of continuing against state that no longer has a home. The README notes that after Unity 2022.2 you can use `destroyCancellationToken` directly in a MonoBehaviour, which removes the extension call entirely.

Coroutine waits replaced by PlayerLoop based tasks

The third bullet describes a PlayerLoop based task API, with `UniTask.Yield`, `UniTask.Delay` and `UniTask.DelayFrame` as the headline members, described as enabling the replacement of all coroutine operations. The mapping is close to one to one with what a `yield return` does:

csharp
await UniTask.DelayFrame(100); 

// replacement of yield return new WaitForSeconds/WaitForSecondsRealtime
await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false);

// yield any playerloop timing(PreUpdate, Update, LateUpdate, etc...)
await UniTask.Yield(PlayerLoopTiming.PreLateUpdate);

// replacement of yield return null
await UniTask.Yield();
await UniTask.NextFrame();

The ability to name a specific `PlayerLoopTiming` is the capability coroutines never offered. A coroutine can only resume at the points Unity exposes as yield instructions, so a system that needs to run just before the LateUpdate phase or on a fixed update boundary had no clean expression. Passing `PlayerLoopTiming.PreLateUpdate` gives you that phase directly.

There is also `UniTask.WaitUntil`, which maps to `WaitUntil` and takes a predicate, plus `UniTask.WaitUntilValueChanged`, a helper that suspends until a property on a target changes. Being able to await a standard `IEnumerator` coroutine means existing code can be adopted one method at a time rather than rewritten all at once.

Thread usage is explicit, and the feature list says so

The fourth bullet is the one people look for first and the one the README is blunt about: UniTask runs completely on Unity's PlayerLoop, so it does not use threads, and it runs on WebGL and wasm. The consequence is that awaiting a UniTask does not move your code to a background thread. If you want that, you ask for it by name.

csharp
await UniTask.SwitchToThreadPool();

// return to MainThread(same as `ObserveOnMainThread` in UniRx)
await UniTask.SwitchToMainThread();

The comparison comment is deliberate and useful. UniRx uses `ObserveOnMainThread` for the same hop, and if your codebase already speaks UniRx the vocabulary transfers directly. The README's table of contents has a section specifically covering ThreadPool limitation, which tells you the author treated this as the most common misunderstanding rather than leaving it to one line in a feature list.

Running on the main thread by default is the right default for a game engine, where almost every Unity API call has to happen on that thread anyway. It also means a continuation lands in a predictable place relative to Update and the rest of the frame loop, which is easier to reason about than a continuation arriving on an arbitrary pool thread in the middle of a frame.

Awaitable events, asynchronous LINQ and a leak tracker

Two features exist specifically to move work off the main thread, and they are the reason some teams adopt UniTask alongside, rather than instead of, a reactive framework. MonoBehaviour message events such as `OnTriggerEnter` or `OnCollisionEnter` can be consumed as an awaitable or as an async enumerable stream, and uGUI button or input events work the same way. An async enumerable sequence means you can write `await foreach` over a message stream instead of writing a callback that re-registers itself.

The README also lists asynchronous LINQ, a `Channel` implementation, and `AsyncReactiveProperty`, which is the observable property type from UniRx reimplemented on the UniTask awaitable. Combined with `SwitchToMainThread`, that gives you familiar operator vocabulary while keeping the awaitable struct based.

For fire and forget work the library offers `UniTaskVoid`, and the README dedicates a section to comparing it with `async void`. The standard guidance applies: `async void` cannot be awaited and its exceptions escape the call site, whereas `UniTaskVoid` keeps them observable. Alongside that sits a TaskTracker window in the editor listing outstanding tasks, so a leak, which in a long running game looks like memory that grows over an hour of play, becomes something you can point at in a window rather than guess at.

Installing as a UPM package or a unitypackage

Getting started is short: install via the UPM package with a git reference, or take the asset package named `UniTask.*.*.*.unitypackage` from the releases page. The repository layout matches a project that also builds for plain .NET. `src/` holds the sources, `Directory.Build.props` carries the shared build settings, `UniTask.NetCore.sln` is the solution used for the .NET Core build, and `docs/` is the generated API reference.

That solution file is the detail worth pausing on. A library whose entire premise is a custom C# 7.0 async method builder has to be compiled by a toolchain that supports task like types, and shipping a separate .NET Core target means the same awaitable can be exercised in unit tests outside the editor rather than only inside it. The README has a section for unit testing and another for UnityEditor only APIs, which is how the two worlds are kept apart.

The repository publishes numbered releases with readable change notes. Version 2.5.11 came out on 2026-05-19 and includes a fix resolving a TreeView deprecation in Unity 6.2 plus CI work moving to a docfx command, while 2.5.10 fixed compilation errors in Unity 2020.1 and older. Version 2.5.9 added `AsyncInstantiateOperation.WithCancellation` and `ToUniTask` for Unity 2022.3.20 and 2023.3 or newer. The last push was on 2026-07-08, and the licence is MIT.

Editorial conclusion

UniTask makes the most sense for projects that have already decided async/await is the right shape for their Unity code and are now paying for it in allocations and a limited set of awaitable operations. Coroutine-heavy codebases gain the most, because the WaitForSeconds, WaitUntil and NextFrame equivalents remove the most boilerplate. The trade is a dependency on a custom async method builder and on PlayerLoop timing, which means code written against UniTask is not portable to plain .NET without changes, and the multi-threading story requires an explicit SwitchToThreadPool call rather than arriving by default. The repository publishes numbered releases with plain change notes, the latest being 2.5.11 on 2026-05-19, and it is MIT licensed with no gateway in front of it. Install the UPM package, convert one coroutine, and check the Unity profiler before committing to a codebase-wide migration.

Frequently asked questions

What is UniTask in Unity?

UniTask is an async/await integration for Unity that uses a struct based UniTask<T> and a custom async method builder, so awaiting does not allocate. It makes Unity AsyncOperations, web requests, coroutines and PlayerLoop timings awaitable, and ships asynchronous LINQ, a Channel and AsyncReactiveProperty on the same awaitable.

Is UniTask free to use?

Yes. The repository is MIT licensed with no paid tier, no per seat cost and no runtime licence check. You can install it from a UPM git reference or as a .unitypackage asset and use it in commercial projects.

Is UniTask multithreaded?

Not by default. The library runs entirely on Unity's PlayerLoop and deliberately avoids threads, which is what lets it work on WebGL and wasm. When you do want background work, you call UniTask.SwitchToThreadPool explicitly and return with UniTask.SwitchToMainThread afterwards.

Official sources

  1. Cysharp/UniTask on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cysharp-unitask.svg)](https://hysenlabs.com/projects/cysharp-unitask)