GDTask 3.0.0
dotnet add package GDTask --version 3.0.0
NuGet\Install-Package GDTask -Version 3.0.0
<PackageReference Include="GDTask" Version="3.0.0" />
<PackageVersion Include="GDTask" Version="3.0.0" />
<PackageReference Include="GDTask" />
paket add GDTask --version 3.0.0
#r "nuget: GDTask, 3.0.0"
#:package GDTask@3.0.0
#addin nuget:?package=GDTask&version=3.0.0
#tool nuget:?package=GDTask&version=3.0.0
GDTask.Nuget
- Targets .NET 6 through .NET 10 with a GodotSharp 4.1.0+ dependency floor.
- This is the Nuget Package version based on code from:
Table of Contents
- Abstract
- Installation via Nuget
- Basic API usage
- Extended Feature Packages
- Task Profiling
- Object Pool Configuration
- Compare with Standard .Net Task API
Abstract
Clarification: Contents in the abstract section are mostly migrated from the Cysharp's UniTask library for Unity
Lightweight async/await integration for Godot
- Struct-based
GDTask<T>with custom AsyncMethodBuilder: synchronously completed tasks require no heap allocation; asynchronous suspensions use pooled, reusable state-machine runners and promise objects instead of allocating a newTaskeach time. - Bypasses
SynchronizationContextandExecutionContextcapture by default, eliminating the per-awaitcontext-save overhead of the standardTaskasync model. - Continuations are dispatched directly onto the Godot player loop (
Process,PhysicsProcess, deferred, and isolated timings), removing the need forTaskScheduleror timer-based bridging. - Provides Godot-specific awaitable primitives - frame delays, signal awaiting,
WaitUntil/WaitWhilewith automaticGodotObjectlifetime tracking, and explicit thread-switching APIs (SwitchToThreadPool,SwitchToMainThread). - Highly compatible surface with
Task/ValueTask/IValueTaskSource(including direct conversion viaAsGDTask/AsTask).
GDTask Under the hood
- Built on the task-like custom async method builder feature of C# 7.0.
GDTask/GDTask<T>arereadonly structtypes holding only a poolableIGDTaskSourcereference and a version token. When a task completes synchronously the result is inlined in the struct with zero heap traffic; when it truly suspends, the async state machine is copied into a pooled runner object, not a newTask, and the runner'sMoveNextdelegate is pre-allocated and reused. - The core completion primitive (
GDTaskCompletionSourceCore<T>) explicitly never captures ExecutionContext or SynchronizationContext. Returning to the main thread is handled by enqueuing continuations into the player-loop-drivenContinuationQueueon the singleton node GDTaskPlayerLoopRunner, not by posting back through a synchronization context.
Installation via Nuget
For .Net CLI
dotnet add package GDTask
For Package Manager Console:
NuGet\Install-Package GDTask
Basic API usage
For more detailed usage, see Unit Tests.
using GodotTask;
// Use GDTaskVoid if this task is only used with ApiUsage().Forget();
public async GDTask ApiUsage()
{
// Delay the execution after frame(s).
await GDTask.DelayFrame(100);
// Delay the execution after delayTimeSpan.
await GDTask.Delay(TimeSpan.FromSeconds(1), DelayType.Realtime);
// Delay the execution until the next Process.
await GDTask.Yield(PlayerLoopTiming.Process);
// The same APIs also accept any IPlayerLoop implementation.
IPlayerLoop processLoop = PlayerLoopRunnerProvider.Process;
await GDTask.Delay(TimeSpan.FromMilliseconds(10), DelayType.DeltaTime, processLoop);
// Delay the execution until the next PhysicsProcess.
await GDTask.WaitForPhysicsProcess();
// Creates a task that will complete at the next provided PlayerLoopTiming when the supplied predicate evaluates to true
await GDTask.WaitUntil(() => Time.GetTimeDictFromSystem()["second"].AsInt32() % 2 == 0);
// Creates a task that will complete at the next provided PlayerLoopTiming when the provided monitorFunction returns a different value.
await GDTask.WaitUntilValueChanged(Time.Singleton, timeInstance => timeInstance.GetTimeDictFromSystem()["second"]);
// Creates an awaitable that asynchronously yields to ThreadPool when awaited.
await GDTask.SwitchToThreadPool();
/* Threaded work */
// Creates an awaitable that yields back to the current or next requested main-thread player-loop update.
await GDTask.SwitchToMainThread();
// If you're already on the main thread but not yet in the requested update phase,
// the continuation resumes on that next requested update instead of completing immediately.
await GDTask.SwitchToMainThread(PlayerLoopTiming.PhysicsProcess);
/* Main thread work */
await GDTask.NextFrame();
// Creates a continuation that executes when the target GDTask completes.
int taskResult = await GDTask.Delay(10).ContinueWith(() => 5);
GDTask<int> task1 = GDTask.Delay(10).ContinueWith(() => 5);
GDTask<string> task2 = GDTask.Delay(20).ContinueWith(() => "Task Result");
GDTask<bool> task3 = GDTask.Delay(30).ContinueWith(() => true);
// Creates a task that will complete when all of the supplied tasks have completed.
var (task1Result, task2Result, task3Result) = await GDTask.WhenAll(task1, task2, task3);
try
{
// Creates a GDTask that has completed with the specified exception.
await GDTask.FromException(new ExpectedException());
}
catch (ExpectedException) { }
try
{
// Creates a GDTask that has completed due to cancellation with the specified cancellation token.
await GDTask.FromCanceled(CancellationToken.None);
}
catch (OperationCanceledException) { }
// Or use an alternative pattern to handle cancellation without exception:
var isCanceled = await GDTask.FromCanceled(CancellationToken.None).SuppressCancellationThrow();
try
{
var source = new CancellationTokenSource();
source.CancelAfter(100);
// Creates a task that never completes, with specified CancellationToken.
await GDTask.Never(source.Token);
}
catch (OperationCanceledException) { }
// Queues the specified work to run on the ThreadPool and returns a GDTask handle for that work.
await GDTask.RunOnThreadPool(
() => GD.Print(Environment.CurrentManagedThreadId.ToString()),
configureAwait: true,
cancellationToken: CancellationToken.None
);
// Create a GDTask that wraps around this task.
await Task.Delay(5).AsGDTask(useCurrentSynchronizationContext: true);
// Associate a time out to the current GDTask.
try
{
await GDTask.Never(CancellationToken.None).Timeout(TimeSpan.FromMilliseconds(5));
}
catch (TimeoutException) { }
}
Extended Feature Packages
The following packages extend the functionality of GDTask; they are optional components for projects where applicable.
AsyncTriggers are no longer included in the main GDTask package. If there is concrete demand for them again, they are intended to live in a separate community package instead.
GDTask.GlobalCancellation
Authored by: @Joy-less
This package adds support for attaching a global cancellation token to GDTasks with the AttachGlobalCancellation extension method and cancelling it with the GDTaskGlobalCancellation.Cancel method. This is useful for cancelling certain tasks in bulk.
Package: GDTask.GlobalCancellation on NuGet
Install:
dotnet add package GDTask.GlobalCancellation
Example usage:
GDTask.Delay(TimeSpan.FromSeconds(3.0), GDTaskGlobalCancellation.GetToken());
GDTask.Delay(TimeSpan.FromSeconds(2.0), GDTaskGlobalCancellation.GetToken());
GDTask.Delay(TimeSpan.FromSeconds(5.0)).AttachGlobalCancellation();
GDTask.Delay(TimeSpan.FromSeconds(1.0)).AttachGlobalCancellation();
GDTaskGlobalCancellation.Cancel();
Task Profiling
Clarification: Contents in the task profiling section are mostly migrated from the Cysharp's UniTask library for Unity
When calling TaskTracker.ShowTrackerWindow() in your code base, the GDTask system will create(or reuse) a GDTask Tracker window for inspecting/diagnosing (leaked) GDTasks.
| Name | Description |
|---|---|
| Enable Tracking | Enable the tracking system for collecting status for future started GDTasks, this is on by default when calling TaskTracker.ShowTrackerWindow(), you may also alter this value through TaskTracker.EnableTracking. |
| Enable StackTrace | Records and show stack traces for the active GDTasks, you may also alter this value through TaskTracker.EnableStackTrace. |
| GC Collect | Invokes GC.Collect() manually. |
- Do keep in mind this feature is for debugging purposes and it has performance penalties, so stay cautious when calling
TaskTracker.ShowTrackerWindow()under the production environment.- The background status collection system does not start if you have never called
TaskTracker.ShowTrackerWindow().- Closing an active
GDTask Trackerwindow does not stop the background status collection system, remember to toggle offEnable Trackingor setTaskTracker.EnableTrackingtofalsein your code.- Godot Games embeds sub-windows by default, you can disable the
Embed Subwindowsoption located inProjectSettings (Advanced Settings enabled)Display/Window/Subwindows/Embed Subwindowsfor them to become Standalone Windows.- This window reacts to the
window closing command(NotificationWMCloseRequest) correctly so it closes itself when you click the close button, to relaunch this window simply callTaskTracker.ShowTrackerWindow()again.
Object Pool Configuration
GDTask internally pools and reuses the promise and state-machine objects behind operations such as Delay, WaitUntil, completion sources, and async runners. TaskPool.MaxPoolSize controls how many objects each individual internal pool is allowed to retain, with a default of int.MaxValue. The value is checked only when a completed object is returned to its pool, so changing it affects future returns only: lowering it does not trim objects that are already pooled, and once a pool is at or above the new limit, additional returns are simply skipped until its size drops below that cap.
Tip: In most projects the default (unbounded) is fine. Consider lowering
MaxPoolSizeonly if you observe unexpectedly high retained memory from pooled GDTask objects — for example, after a burst of thousands of concurrent tasks that are unlikely to recur.
Compare with Standard .Net Task API
Clarification: Contents in the compare section are mostly migrated from the Cysharp's UniTask library for Unity
Same as the Standard .Net Task APIs, CancellationToken and CancellationTokenSource are widely used by the GDTask APIs as well.<br>
Otherwise, the following table shows the GDTask APIs provided that are meant to replace the usage of standard .Net Task APIs.
| .NET Type | GDTask Type |
|---|---|
Task/ValueTask |
GDTask |
Task<T>/ValueTask<T> |
GDTask<T> |
async void |
async GDTaskVoid |
+= async () => { } |
GDTask.Void, GDTask.Action |
| --- | GDTaskCompletionSource |
TaskCompletionSource<T> |
GDTaskCompletionSource<T>/AutoResetGDTaskCompletionSource<T> |
ManualResetValueTaskSourceCore<T> |
GDTaskCompletionSourceCore<T> |
IValueTaskSource |
IGDTaskSource |
IValueTaskSource<T> |
IGDTaskSource<T> |
ValueTask.IsCompleted |
GDTask.Status.IsCompleted() |
ValueTask<T>.IsCompleted |
GDTask<T>.Status.IsCompleted() |
CancellationToken.Register(UnsafeRegister) |
CancellationToken.RegisterWithoutCaptureExecutionContext |
CancellationTokenSource.CancelAfter |
CancellationTokenSource.CancelAfterSlim |
Task.Delay |
GDTask.Delay |
Task.Yield |
GDTask.Yield |
Task.Run |
GDTask.RunOnThreadPool |
Task.WhenAll |
GDTask.WhenAll |
Task.WhenAny |
GDTask.WhenAny |
Task.WhenEach |
GDTask.WhenEach |
Task.CompletedTask |
GDTask.CompletedTask |
Task.FromException |
GDTask.FromException |
Task.FromResult |
GDTask.FromResult |
Task.FromCanceled |
GDTask.FromCanceled |
Task.ContinueWith |
GDTask.ContinueWith |
TaskScheduler.UnobservedTaskException |
GDTaskExceptionHandler.UnobservedTaskException |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Godot.SourceGenerators (>= 4.1.0)
- GodotSharp (>= 4.1.0)
-
net6.0
- Godot.SourceGenerators (>= 4.1.0)
- GodotSharp (>= 4.1.0)
-
net7.0
- Godot.SourceGenerators (>= 4.1.0)
- GodotSharp (>= 4.1.0)
-
net8.0
- Godot.SourceGenerators (>= 4.1.0)
- GodotSharp (>= 4.1.0)
-
net9.0
- Godot.SourceGenerators (>= 4.1.0)
- GodotSharp (>= 4.1.0)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on GDTask:
| Package | Downloads |
|---|---|
|
BenchmarkDotNetGodot
BenchmarkDotNet.Godot allows developers to easily conduct performance testing and benchmarking within the Godot engine, enabling them to assess the efficiency of their code and identify potential performance bottlenecks. |
|
|
BenchmarkDotNetGodot.GDTask
Package Description |
|
|
GDTask.GlobalCancellation
Adds global cancellations to GDTask.Nuget |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 170 | 4/3/2026 |
| 2.0.2 | 223 | 3/10/2026 |
| 2.0.1 | 131 | 3/5/2026 |
| 2.0.0 | 143 | 2/25/2026 |
| 1.7.0 | 909 | 12/20/2025 |
| 1.6.1 | 263 | 10/20/2025 |
| 1.6.0 | 200 | 10/20/2025 |
| 1.5.0 | 283 | 7/1/2025 |
| 1.4.1 | 456 | 4/6/2025 |
| 1.4.0 | 197 | 4/6/2025 |
| 1.3.0 | 499 | 12/4/2024 |
| 1.2.2 | 768 | 3/29/2024 |
| 1.2.1 | 215 | 3/28/2024 |
| 1.2.0 | 214 | 2/27/2024 |
| 1.1.0 | 200 | 2/26/2024 |
| 1.0.0 | 231 | 2/22/2024 |
| 0.0.3 | 865 | 2/2/2024 |
| 0.0.2 | 212 | 1/29/2024 |
| 0.0.1 | 183 | 1/28/2024 |
- Breaking: `AsyncTriggers` are no longer included in the main GDTask package.
- Breaking: non-generic object-state overloads of `RunOnThreadPool` were removed; use the generic `TState` overloads instead.
- Changed `GDTask.WhenAll` to wait for every input before completing.
- Changed `GDTask.WhenAny` and `GDTask.WhenEach` semantics to better align with Microsoft's official `Task.WhenAny` and `Task.WhenEach` behavior.
- Added `IPlayerLoop`-based overloads across scheduling, delay, timeout, cancellation, and waiting APIs.
- Added simpler `CancellationToken` overloads for `GDTask.WaitUntil`, `GDTask.WaitWhile`, `GDTask.Delay`, and `GDTask.DelayFrame`.
- Added collection-friendly coordination improvements, including broader `IEnumerable` support for `GDTask.WhenAll` and `GDTask.WhenAny`, plus shorthand await support for task collections.
- Added public pool configuration through `TaskPool.MaxPoolSize`.
- Fixed `GDTask.WhenEach`, `SwitchToMainThread`, `TimeoutWithoutException`, `AsyncLazy`, and `GDTaskCompletionSource` semantics in edge-case and concurrency scenarios.