Skip to content
Back to skills

Async Programming

ASecurity

Async programming patterns with UniTask, cancellation tokens, and exception handling. Use when writing async code, handling CancellationTokenSource lifecycle, using SuppressToResultAsync, implementing detached UniTask/UniTaskVoid flows, or working with Result/EnumResult types for exception-free flow propagation.

  • 23 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added May 27, 2026
businessrustgoapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add decentraland/unity-explorer --skill async-programming --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Async Programming?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Async Programming
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/decentraland-async-programming/badge)](https://www.skillsdirectory.com/skills/decentraland-async-programming)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: async-programming
description: "Async programming patterns with UniTask, cancellation tokens, and exception handling. Use when writing async code, handling CancellationTokenSource lifecycle, using SuppressToResultAsync, implementing detached UniTask/UniTaskVoid flows, or working with Result/EnumResult types for exception-free flow propagation."
user-invocable: false
---

# Async Programming Patterns

## Sources

- `docs/async-programming.md` — Async pattern guidelines for UniTask flows
- `docs/architecture-overview.md` — Result struct and exception-free flow details

---

## Core Rules

> Condensed rules are in CLAUDE.md §9. This skill provides expanded patterns and code examples not covered there.

## Exception Handling Pattern

### Wrong — Unhandled exceptions in detached flow

```csharp
// BAD: Exception kills the flow silently
async UniTaskVoid DoSomethingAsync(CancellationToken ct)
{
    var result = await webRequestController.GetAsync(args, ct);
    // If this throws, nobody catches it
}
```

### Correct — Proper exception handling

```csharp
async UniTaskVoid DoSomethingAsync(CancellationToken ct)
{
    try
    {
        var result = await webRequestController.GetAsync(args, ct);
        ProcessResult(result);
    }
    catch (OperationCanceledException) { }  // Normal cancellation — ignore
    catch (Exception exception)
    {
        ReportHub.LogException(exception, ReportCategory.GENERIC_WEB_REQUEST);
    }
}
```

### Code Example — Exception Handling in Controller

From `MinimapController.cs`:

```csharp
private async UniTask<PlacesData.PlaceInfo?> GetPlaceInfoAsync(
    Vector2Int parcelPosition, CancellationToken ct, bool renewCache = false)
{
    try
    {
        return await placesAPIService.GetPlaceAsync(parcelPosition, ct, renewCache);
    }
    catch (OperationCanceledException _) { }
    catch (NotAPlaceException notAPlaceException)
    {
        ReportHub.LogWarning(ReportCategory.UNSPECIFIED,
            $"Not a place requested: {notAPlaceException.Message}");
    }
    catch (Exception exception)
    {
        ReportHub.LogException(exception, ReportCategory.GENERIC_WEB_REQUEST);
    }

    return null;
}
```

## SuppressToResultAsync Pattern

Wraps a `UniTask<T>` in a try/catch and returns a `Result<T>` struct instead of throwing.

### Code Example — SuppressToResultAsync + Result

From `TokenFileAuthenticator.cs`:

```csharp
private async UniTask<IWeb3Identity> LoginAsync(CancellationToken ct)
{
    if (!File.Exists(TOKEN_PATH))
        throw new AutoLoginTokenNotFoundException();

    // SuppressToResultAsync catches exceptions and wraps in Result<string>
    Result<string> contentResult = await File.ReadAllTextAsync(TOKEN_PATH, ct)!
        .SuppressToResultAsync<string>(ReportCategory.AUTHENTICATION);

    if (contentResult.Success == false)
        throw new Exception(contentResult.ErrorMessage ?? "Cannot read token file");

    string token = contentResult.Value;
    // ... continue with token ...
}
```

## Result and EnumResult Types

### Result Struct

Zero-cost value type for exception-free flow propagation:

```csharp
// Success
return Result<string>.SuccessResult(value);

// Error
return Result<string>.ErrorResult("Something went wrong");

// Check
if (result.Success)
    UseValue(result.Value);
else
    HandleError(result.ErrorMessage);
```

### REnum Union Types

Roslyn Source Generator for Rust-style enums. Zero-cost pattern matching:

```csharp
// Pattern matching via .Match()
result.Match(
    success: value => ProcessValue(value),
    error: msg => LogError(msg)
);

// Safe access via .IsXXX(out T)
if (result.IsSuccess(out var value))
    ProcessValue(value);
```

## Cancellation Token Management

### CancellationTokenSource Lifecycle

From `MinimapController.cs` — proper CTS management:

```csharp
private CancellationTokenSource? placesApiCts;

protected override void OnFocus()
{
    // Cancel any in-flight request
    placesApiCts.SafeCancelAndDispose();
    placesApiCts = new CancellationTokenSource();
    RefreshPlaceInfoUIAsync(previousParcelPosition, placesApiCts.Token).Forget();
}

// SafeRestart = cancel + dispose + create new
private void OnFavoriteButtonClicked(bool value)
{
    favoriteCancellationToken = favoriteCancellationToken.SafeRestart();
    SetAsFavoriteAsync(favoriteCancellationToken.Token).Forget();
}

public override void Dispose()
{
    placesApiCts.SafeCancelAndDispose();
    disposeCts.Cancel();
    favoriteCancellationToken.SafeCancelAndDispose();
}
```

**Key patterns:**
- `SafeCancelAndDispose()` — Cancel and dispose in one call, null-safe
- `SafeRestart()` — Cancel + dispose + create new CTS
- Always dispose CTS in `Dispose()`
- Use a dedicated `disposeCts` for operations that should cancel on controller disposal

### Cancellation Checking

```csharp
// CORRECT — Check without throwing (cheap)
if (ct.IsCancellationRequested)
    return;

// WRONG — Throws exception (expensive)
ct.ThrowIfCancellationRequested();
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.