louthy/language-ext · info · OperationCanceledException
OperationCanceledException
Error message
OperationCanceledException
What it means
Iterable.AsyncEnumerable.AsEnumerableIO converts an IO<IAsyncEnumerable<A>> into a synchronous IEnumerable<A> by blocking on ToBlockingEnumerable with the environment token. If that token is cancelled, both ToBlockingEnumerable and the explicit per-element check throw OperationCanceledException. The library surfaces cancellation this way so callers can abort a blocking enumeration deterministically.
Solutions
- Catch OperationCanceledException around the enumeration and treat it as graceful shutdown
- Increase or remove the timeout on the CancellationTokenSource feeding EnvIO.New
- Consume the source asynchronously with AsAsyncEnumerableIO() and await foreach instead of blocking
- Ensure the async producer completes or yields control so the blocking drain is not starved while the token stays alive
Example fix
// before
foreach (var x in iterable.AsEnumerableIO().Run(env)) { ... }
// after
try
{
foreach (var x in iterable.AsEnumerableIO().Run(env)) { ... }
}
catch (OperationCanceledException) when (env.Token.IsCancellationRequested)
{
// cancellation requested: exit cleanly
} Defensive patterns
Strategy: try-catch
Validate before calling
if (env.Token.IsCancellationRequested) return; // don't start a blocking drain on a cancelled token
Try / catch
try { foreach (var x in seq) { ... } }
catch (OperationCanceledException) when (token.IsCancellationRequested) { /* cancelled */ } Prevention
- Prefer async consumption (await foreach over AsAsyncEnumerableIO) over blocking ToBlockingEnumerable
- Avoid bridging async sources to sync code with short-lived cancellation tokens
- Set token timeouts generously relative to the slowest possible producer
When it happens
Trigger: Calling AsEnumerableIO() on an Iterable whose underlying source is an IAsyncEnumerable<A>, then enumerating the returned sequence after the EnvIO token was cancelled (cts.Cancel(), timeout expiry, linked token fired); the check fires before each yield return at line 28.
Common situations: Bridging async streams to sync code (unit tests, legacy callers) with a test timeout cancelling the token; host shutdown mid-drain; using CancellationTokenSource.CreateLinkedTokenSource with a deadline that elapses during a slow async producer.
Related errors
- OperationCanceledException
- OperationCanceledException
- OperationCanceledException
- OperationCanceledException
- OperationCanceledException
AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15).
Data as JSON: /api/errors/539b6fb0d65497d3.
Report an issue: GitHub.
Appendix: source
Thrown at LanguageExt.Core/Immutable Collections/Iterable/DSL/Iterable.AsyncEnumerable.cs:28
sealed class IterableAsyncEnumerable<A>(IO<IAsyncEnumerable<A>> runEnumerable) : Iterable<A>
{
internal override bool IsAsync =>
true;
public override IO<int> CountIO() =>
IO.liftVAsync(async env => await (await runEnumerable.RunAsync(env)).CountAsync(env.Token));
public override IO<IEnumerable<A>> AsEnumerableIO()
{
return IO.lift(env => go(env, runEnumerable));
static IEnumerable<A> go(EnvIO env, IO<IAsyncEnumerable<A>> run)
{
var xs = run.Run(env);
foreach (var x in xs.ToBlockingEnumerable(env.Token))
{
if (env.Token.IsCancellationRequested) throw new OperationCanceledException();
yield return x;
}
}
}
public override IO<IAsyncEnumerable<A>> AsAsyncEnumerableIO()
{
return IO.lift(env => go(env, runEnumerable));
static async IAsyncEnumerable<A> go(EnvIO env, IO<IAsyncEnumerable<A>> run)
{
var xs = await run.RunAsync(env);
await foreach (var x in xs.WithCancellation(env.Token))
{
yield return x;
}
}
}View on GitHub (pinned to 2f0e362824)