Usage
Task Services protects critical async operations from app backgrounding interruption
Essential Kit's Task Services wraps platform-specific background task APIs into a unified async/await interface. When users background your app, Task Services requests additional execution time from the OS to complete critical operations like saving game state or uploading data.
Table of Contents
Import Namespaces
using VoxelBusters.EssentialKit;
using VoxelBusters.CoreLibrary;Understanding Background Task Protection
When a user presses the home button or switches apps, mobile operating systems transition your app through lifecycle states:
Normal Flow (without Task Services):
User presses Home → App enters Background → OS suspends app after ~1 second → Task interrupted ❌Protected Flow (with Task Services):
User presses Home → Task Services requests background time → App continues for 30s (iOS) / minutes (Android) → Task completes ✅System Limits: Task Services extends background time but cannot eliminate system limits. iOS allows ~30 seconds, Android allows a few minutes. Design tasks to complete within these limits or implement checkpoint/resume patterns.
Core API: AllowRunningApplicationInBackgroundUntilTaskCompletion
The primary method protects any async Task from backgrounding interruption:
public static Task AllowRunningApplicationInBackgroundUntilTaskCompletion(
Task task,
Callback onBackgroundProcessingQuotaWillExpireCallback = null
)task
Task
The async operation to protect from backgrounding
onBackgroundProcessingQuotaWillExpireCallback
Callback
Optional callback when background time is about to expire
Returns: Continuation task that completes when the original task finishes and handles cleanup.
Generic Overload for Task<TResult>
For tasks that return values:
public static Task<TResult> AllowRunningApplicationInBackgroundUntilTaskCompletion<TResult>(
Task<TResult> task,
Callback onBackgroundProcessingQuotaWillExpireCallback = null
)Preserves the task result while providing background protection.
Extension Methods for Cleaner Syntax
Task Services provides extension methods on Task and Task<TResult>:
public static Task AllowRunningApplicationInBackgroundUntilCompletion(
this Task task,
Callback onBackgroundProcessingQuotaWillExpireCallback = null
)
public static Task<TResult> AllowRunningApplicationInBackgroundUntilCompletion<TResult>(
this Task<TResult> task,
Callback onBackgroundProcessingQuotaWillExpireCallback = null
)Extension methods provide identical functionality with more convenient syntax.
Pattern 1: Save Game Data on Pause
Protect save operations when the user backgrounds the app:
using VoxelBusters.EssentialKit;
using VoxelBusters.CoreLibrary;
public class GameManager : MonoBehaviour
{
async void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
Debug.Log("App backgrounding - initiating protected save");
// Create save task
var saveTask = SaveGameDataAsync();
// Protect with background execution
await TaskServices.AllowRunningApplicationInBackgroundUntilTaskCompletion(
saveTask,
onBackgroundProcessingQuotaWillExpireCallback: () =>
{
Debug.LogWarning("Background time expiring - save may be interrupted");
// Perform emergency cleanup if needed
MarkSaveAsPartial();
}
);
Debug.Log("Save completed successfully");
}
else
{
Debug.Log("App resuming from background");
}
}
async Task SaveGameDataAsync()
{
Debug.Log("Saving player progress...");
await SavePlayerProgress();
Debug.Log("Saving inventory...");
await SaveInventory();
Debug.Log("Uploading to cloud...");
await UploadToCloud();
Debug.Log("All save operations completed");
}
async Task SavePlayerProgress()
{
await Task.Delay(1000); // Simulate save operation
PlayerPrefs.SetInt("PlayerLevel", 10);
PlayerPrefs.Save();
}
async Task SaveInventory()
{
await Task.Delay(500); // Simulate inventory save
// Save inventory data
}
async Task UploadToCloud()
{
await Task.Delay(2000); // Simulate cloud upload
// Upload save to cloud service
}
void MarkSaveAsPartial()
{
PlayerPrefs.SetInt("SaveIncomplete", 1);
PlayerPrefs.Save();
Debug.Log("Marked save as incomplete for recovery on next launch");
}
}Pattern 2: Extension Method Syntax
Use extension methods for cleaner, more readable code:
using VoxelBusters.EssentialKit;
using VoxelBusters.CoreLibrary;
public class AnalyticsManager : MonoBehaviour
{
public async void UploadAnalytics()
{
try
{
Debug.Log("Starting analytics upload");
var uploadTask = PerformAnalyticsUploadAsync();
// Extension method syntax - cleaner and more natural
await uploadTask.AllowRunningApplicationInBackgroundUntilCompletion(
onBackgroundProcessingQuotaWillExpireCallback: () =>
{
Debug.LogWarning("Upload may be interrupted - saving partial data");
SavePartialAnalyticsData();
}
);
Debug.Log("Analytics upload completed successfully");
}
catch (Exception ex)
{
Debug.LogError($"Analytics upload failed: {ex.Message}");
}
}
async Task PerformAnalyticsUploadAsync()
{
// Batch analytics events
var events = GatherAnalyticsEvents();
// Simulate network upload
await Task.Delay(3000);
// Upload to analytics service
Debug.Log($"Uploaded {events.Count} analytics events");
}
List<string> GatherAnalyticsEvents()
{
return new List<string> { "level_complete", "item_purchased", "achievement_unlocked" };
}
void SavePartialAnalyticsData()
{
Debug.Log("Saving analytics events locally for next upload");
// Save events to local storage for retry
}
}Pattern 3: Task with Return Value
Protect operations that return results:
using VoxelBusters.EssentialKit;
using VoxelBusters.CoreLibrary;
public class CloudSyncManager : MonoBehaviour
{
async void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
var syncTask = SyncWithCloudAsync();
// Generic overload preserves return value
bool syncSuccess = await TaskServices.AllowRunningApplicationInBackgroundUntilTaskCompletion(
syncTask,
onBackgroundProcessingQuotaWillExpireCallback: () =>
{
Debug.LogWarning("Cloud sync may timeout");
}
);
if (syncSuccess)
{
Debug.Log("Cloud sync completed successfully");
}
else
{
Debug.LogError("Cloud sync failed");
}
}
}
async Task<bool> SyncWithCloudAsync()
{
try
{
Debug.Log("Starting cloud synchronization");
// Upload local changes
await UploadLocalChanges();
// Download remote changes
await DownloadRemoteChanges();
// Merge data
await MergeCloudData();
Debug.Log("Cloud sync completed");
return true;
}
catch (Exception ex)
{
Debug.LogError($"Cloud sync error: {ex.Message}");
return false;
}
}
async Task UploadLocalChanges()
{
await Task.Delay(1000);
Debug.Log("Local changes uploaded");
}
async Task DownloadRemoteChanges()
{
await Task.Delay(1500);
Debug.Log("Remote changes downloaded");
}
async Task MergeCloudData()
{
await Task.Delay(500);
Debug.Log("Data merged successfully");
}
}Pattern 4: Multiple Protected Tasks
You can protect multiple independent tasks:
using VoxelBusters.EssentialKit;
using VoxelBusters.CoreLibrary;
public class DataManager : MonoBehaviour
{
async void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
Debug.Log("App backgrounding - protecting multiple operations");
// Create multiple tasks
var saveTask = SaveGameDataAsync();
var uploadTask = UploadAnalyticsAsync();
var cleanupTask = CleanupResourcesAsync();
// Protect all tasks with background execution
// Note: All tasks share the same background time quota
await Task.WhenAll(
saveTask.AllowRunningApplicationInBackgroundUntilCompletion(),
uploadTask.AllowRunningApplicationInBackgroundUntilCompletion(),
cleanupTask.AllowRunningApplicationInBackgroundUntilCompletion()
);
Debug.Log("All background operations completed");
}
}
async Task SaveGameDataAsync()
{
await Task.Delay(1000);
Debug.Log("Game data saved");
}
async Task UploadAnalyticsAsync()
{
await Task.Delay(1500);
Debug.Log("Analytics uploaded");
}
async Task CleanupResourcesAsync()
{
await Task.Delay(500);
Debug.Log("Resources cleaned up");
}
}Shared Background Quota: All protected tasks share the same background time quota (30s on iOS, minutes on Android). Design tasks to complete within this limit or they may all be terminated together.
Handling Quota Expiration
Always implement quota expiration callbacks for graceful degradation:
using VoxelBusters.EssentialKit;
using VoxelBusters.CoreLibrary;
public class RobustSaveManager : MonoBehaviour
{
async void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
var saveTask = SaveWithCheckpointingAsync();
await saveTask.AllowRunningApplicationInBackgroundUntilCompletion(
onBackgroundProcessingQuotaWillExpireCallback: () =>
{
Debug.LogWarning("Background quota expiring - implementing emergency save");
// Quick save critical data only
SaveCriticalDataSynchronously();
// Set flag to resume on next launch
PlayerPrefs.SetInt("ResumeIncompleteSave", 1);
PlayerPrefs.Save();
}
);
}
}
async Task SaveWithCheckpointingAsync()
{
// Save in stages with checkpoints
await SaveStage1();
SetCheckpoint(1);
await SaveStage2();
SetCheckpoint(2);
await SaveStage3();
SetCheckpoint(3);
ClearCheckpoint();
Debug.Log("Complete save finished");
}
async Task SaveStage1()
{
await Task.Delay(800);
Debug.Log("Stage 1: Player stats saved");
}
async Task SaveStage2()
{
await Task.Delay(800);
Debug.Log("Stage 2: Inventory saved");
}
async Task SaveStage3()
{
await Task.Delay(800);
Debug.Log("Stage 3: World state saved");
}
void SetCheckpoint(int stage)
{
PlayerPrefs.SetInt("SaveCheckpoint", stage);
PlayerPrefs.Save();
}
void ClearCheckpoint()
{
PlayerPrefs.DeleteKey("SaveCheckpoint");
PlayerPrefs.Save();
}
void SaveCriticalDataSynchronously()
{
// Emergency save - only critical data, synchronous
PlayerPrefs.SetInt("PlayerLevel", 10);
PlayerPrefs.SetInt("LastKnownHealth", 75);
PlayerPrefs.Save();
Debug.Log("Emergency save completed");
}
}Data Properties
TaskServices.AllowRunningApplicationInBackgroundUntilTaskCompletion
Method
Wraps a Task so Essential Kit requests platform background time until the work completes.
TaskServices.AllowRunningApplicationInBackgroundUntilTaskCompletion<TResult>
Method
Same as above but preserves the original task’s return value.
Task.AllowRunningApplicationInBackgroundUntilCompletion
Extension Method
Fluent syntax for protecting tasks without additional wrappers.
Callback onBackgroundProcessingQuotaWillExpireCallback
Callback
Invoked when the OS is about to reclaim background time; use it for emergency saves or cleanup.
TaskServicesUnitySettings.IsEnabled
bool
Confirms whether Task Services is active. If false, the helper methods will log an error instead of protecting the task.
Core APIs Reference
TaskServices.AllowRunningApplicationInBackgroundUntilTaskCompletion(task, callback)
Protect async Task from backgrounding
Task continuation
TaskServices.AllowRunningApplicationInBackgroundUntilTaskCompletion<T>(task, callback)
Protect Task<T> preserving result
Task<T> continuation
task.AllowRunningApplicationInBackgroundUntilCompletion(callback)
Extension method for Task
Task continuation
task.AllowRunningApplicationInBackgroundUntilCompletion<T>(callback)
Extension method for Task<T>
Task<T> continuation
Error Handling
Feature disabled or not initialized
Helper logs “Feature is not active or not yet initialised.”
Enable Task Services in Essential Kit Settings or call TaskServices.Initialize before awaiting background-protected tasks.
Background quota expires early
Long-running operation exceeds OS limit
Use the quota callback to persist critical state synchronously and resume the workload on the next foreground session.
Exceptions thrown inside protected task
await raises an exception after background completion
Wrap protected calls in try/catch blocks so you can surface telemetry, retry on resume, or notify the player.
Nested protection
Same task wrapped more than once
Guard against double-wrapping by tracking outstanding tasks or creating helper methods that centralize protection.
Android background restrictions
Device kills background work while in Doze/battery saver
Pair protected tasks with user-visible progress (e.g., foreground service) or postpone heavy uploads until the device is active.
Important Behaviors
Background Time Quota:
iOS: Approximately 30 seconds of background execution time
Android: Few minutes depending on device and Android version
System may terminate earlier if resources are critically low
Shared Quota:
All tasks protected in a single app share the same background time quota
Multiple
AllowRunningApplicationInBackgroundUntilTaskCompletioncalls don't multiply available time
Callback Timing:
onBackgroundProcessingQuotaWillExpireCallbackis called shortly before system terminationUse this callback for emergency cleanup, not to extend execution time
Task Lifetime:
Protected tasks continue running even when the entire app is in background state
All other Unity MonoBehaviour code also continues executing during background time
Best Practice: Design tasks to complete in under 20 seconds to have safety margin before iOS quota expiration. For longer operations, implement checkpoint/resume patterns.
Advanced: Manual Initialization
Manual initialization is only needed for specific runtime scenarios. For most games, Essential Kit's automatic initialization handles everything. Skip this section unless you need runtime configuration or custom task monitoring.
Understanding Manual Initialization
Default Behavior: Essential Kit automatically initializes Task Services using global settings from the ScriptableObject configured in Unity Editor.
Advanced Usage: Override settings at runtime when you need:
Custom background task timeout monitoring
Different task priority levels for various operations
Runtime task execution policies based on battery level or device capabilities
Implementation
void Awake()
{
var settings = new TaskServicesUnitySettings(isEnabled: true);
TaskServices.Initialize(settings);
}Related Guides
Demo scene:
Assets/Plugins/VoxelBusters/EssentialKit/Examples/Scenes/TaskServicesDemo.unityUse with Cloud Services to sync game state on backgrounding
Pair with Network Services to verify connectivity before uploads
See Testing Guide for device testing checklist
Last updated
Was this helpful?