{"record":{"id":"03f1ecfe57e687d4","repo":"louthy/language-ext","slug":"operationcanceledexception-observableext","errorCode":null,"errorMessage":"OperationCanceledException","messagePattern":"OperationCanceledException","errorType":"exception","errorClass":"OperationCanceledException","httpStatus":null,"severity":"warning","filePath":"LanguageExt.Core/Extensions/ObservableExt.cs","lineNumber":71,"sourceCode":"        }\n\n        public static async IAsyncEnumerable<A> Run(\n            IObservable<A> observable, \n            [EnumeratorCancellation] CancellationToken token)\n        {\n            using var wait  = new AutoResetEvent(false);\n            var       queue = new ConcurrentQueue<Fin<A>>();\n            observable.Subscribe(new Observe<A>(wait, queue));\n\n            while (true)\n            {\n                await wait.WaitOneAsync(token).ConfigureAwait(false);\n                while (queue.TryDequeue(out var item))\n                {\n                    if (item.IsFail)\n                    {\n                        if (item.FailValue == Errors.None) yield break;\n                        if (item.FailValue == Errors.Cancelled) throw new OperationCanceledException();\n                        item.FailValue.Throw();\n                        yield break;\n                    }\n                    else\n                    {\n                        yield return item.SuccValue;\n                    }\n                }\n            }                \n        }\n\n        public void OnCompleted()\n        {\n            queue.Enqueue(Errors.None);\n            wait.Set();\n        }\n\n        public void OnError(Exception error)","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/louthy/language-ext/blob/2f0e3628242889774d4141960a35671a0280051f/LanguageExt.Core/Extensions/ObservableExt.cs#L53-L89","documentation":"ObservableExt's buffering/consume loop rethrows cancellation: when a queued item is a Fail whose error is Errors.Cancelled, the iterating coroutine throws OperationCanceledException. This signals that the underlying async stream/token was cancelled rather than a normal error.","triggerScenarios":"Iterating the observable extension while the source completes with Errors.Cancelled (cancellation token fired upstream), e.g. via a CancellationToken passed to the producing async operation.","commonSituations":"User-initiated cancellation of long-running observable pipelines, host shutdown, or timeout tokens firing while an observable consumer is mid-enumeration.","solutions":["Catch OperationCanceledException around enumeration and treat it as normal cancellation.","Check the CancellationToken.IsCancellationRequested before/while consuming.","Pass the token through so cancellation is cooperative rather than exception-based."],"exampleFix":"// before\nforeach (var x in observableExt) Process(x);\n// after\ntry { foreach (var x in observableExt) Process(x); }\ncatch (OperationCanceledException) { /* expected on shutdown */ }","handlingStrategy":"try-catch","validationCode":"if (token.IsCancellationRequested) return; // check before enumerating","typeGuard":null,"tryCatchPattern":"try { foreach (var x in obs) { } } catch (OperationCanceledException) { /* cancelled: expected */ }","preventionTips":["Pass CancellationToken through the whole pipeline","Handle Errors.Cancelled distinctly from real failures","Treat OperationCanceledException as control flow, not failure"],"tags":["cancellation","observable","async"],"backgroundTag":"operation-cancelled","analyzedSha":"2f0e3628242889774d4141960a35671a0280051f","analyzedAt":"2026-09-15T03:31:55.716Z","contentChangedAt":"2026-09-15T03:31:55.716Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}