Async patterns
Consume IConvaiOperation<T> and IConvaiStream<T> using async/await, coroutines, chaining, progress tracking, cancellation, and streams.
IConvaiOperation<T> and IConvaiStream<T> support multiple consumption patterns so you can use the style that fits your codebase — pure async/await, Unity coroutines, or a mix of both. For type definitions and member references, see Operation & Stream Types.
Async/await
The most direct pattern. Works in any async method. Faulted operations throw ConvaiOperationException.
using Convai.Runtime.Core.Async;
using Convai.Runtime.Facades;
using System.Threading;
using UnityEngine;
public class AsyncConnectExample : MonoBehaviour
{
private async void Start()
{
var manager = ConvaiManager.ActiveManager;
if (manager == null) return;
try
{
var session = await manager.ConnectAsync(destroyCancellationToken);
Debug.Log($"Connected. Room: {session.RoomId}");
}
catch (ConvaiOperationException ex)
{
Debug.LogError($"[{ex.Code}] Connect failed: {ex.Message}");
}
catch (OperationCanceledException)
{
Debug.Log("Connect canceled — scene unloaded.");
}
}
}await operation vs. await operation.AsTask()
Both produce the same result. Prefer await operation directly — it uses GetAwaiter() and avoids an extra allocation. Use AsTask() only when you need to pass the operation to a method that requires Task<T>, such as Task.WhenAll.
Coroutines
Use coroutines when your script cannot use async (e.g., on Unity event callbacks that do not support async), or when you prefer a callback-based flow.
ToCoroutine yields until the operation completes. Both onSuccess and onError are optional — pass null for either if you do not need the callback.
In coroutines, faulted operations do not throw. If you omit the onError callback, failures are silent.
ContinueWith chaining
Transform the result of one operation into another without nesting await calls.
ContinueWith propagates errors: if the source operation faults, the chained operation also faults with the same error without invoking the selector.
Progress tracking
Poll operation.Progress to drive a UI progress indicator. The value advances from 0.0 to 1.0 as the operation completes. Not all operations report granular progress — check Status for definitive completion.
Cancellation
Using CancellationToken
Pass a CancellationToken to any SDK method. The operation transitions to Canceled when the token is signaled.
Using destroyCancellationToken
MonoBehaviour.destroyCancellationToken is the simplest cancellation pattern for operations scoped to a component's lifetime — the token is automatically signaled when the component is destroyed.
Using operation.Cancel()
Call Cancel() on the operation handle directly for manual cancellation independent of a CancellationToken.
CancellationToken vs. operation.Cancel()
CancellationToken
operation.Cancel()
Source
External (CancellationTokenSource)
The operation handle
Use case
Component lifetime, timeout, linked cancellation
Single operation cancel from a button or event
Coroutine compatible
Pass at operation creation
Call on the handle at any time
Streams
Use IConvaiStream<T> with await foreach and await using for resource cleanup.
ReadAllAsync returns IAsyncEnumerable<T>. The loop exits when the stream reaches Completed, Faulted, or Canceled. Always wrap in await using so DisposeAsync() is called even if the loop exits early.
Never block the Unity main thread with .Result — it deadlocks.
Use await or ToCoroutine() instead.
Always dispose streams. Failing to await using a stream leaks the underlying resources.
Error handling decision table
async void MonoBehaviour method
Async/await with try/catch
Natural fit; faults surface as exceptions
UI button callback (no async support)
Coroutine with onError callback
Button callbacks are synchronous
Sequential operations with transforms
ContinueWith chaining
Avoids nested awaits; propagates errors automatically
Progress bar or loading overlay
Coroutine polling Progress
yield return null loop per frame
Component lifetime scoping
destroyCancellationToken
Zero boilerplate; automatic cleanup
User-triggered cancel (button)
operation.Cancel()
Direct handle control without CTS
Continuous data stream
await foreach + await using
IAsyncEnumerable + guaranteed disposal
Usage examples
Example 1 — Loading overlay with progress bar and cancel button
A medical training simulation shows a loading overlay while the session connects, with a visual progress bar and a cancel button for learners who want to exit before the session starts.
Example 2 — Streaming transcript tokens to a custom log widget
A corporate onboarding simulation streams individual transcript tokens from Convai and appends them to a custom log widget one token at a time, producing a typewriter-style effect.
Troubleshooting
Operation stays in Running indefinitely
SDK method awaiting a response that never arrives (network timeout)
Set a CancellationToken with a timeout: new CancellationTokenSource(TimeSpan.FromSeconds(15)).Token
HasError is true but ConvaiOperationException is not thrown
Using coroutine path — errors are delivered to onError callback, not thrown
Add an onError callback to ToCoroutine()
catch (ConvaiOperationException) block never hit on cancellation
Cancellation throws OperationCanceledException, not ConvaiOperationException
Add a separate catch (OperationCanceledException) block
Cancellation has no effect after operation.Cancel()
Operation already completed before cancel was called
Check IsCompleted before calling Cancel()
Stream hangs on scene unload
ReadAllAsync loop not passing a CancellationToken
Pass destroyCancellationToken to ReadAllAsync
ContinueWith selector never runs
Source operation faulted; error is propagated, selector is skipped
Check the chained operation's HasError or catch ConvaiOperationException at the await site
Next steps
For the full type reference behind these patterns, see Operation & Stream Types. For SDK methods that return IConvaiOperation<T>, see ConvaiManager API and Character & Player API.
Last updated
Was this helpful?