{"record":{"id":"7749128bea4c031b","repo":"Cysharp/UniTask","slug":"enumerator-is-already-running-does-not-allow-call","errorCode":null,"errorMessage":"Enumerator is already running, does not allow call GetAsyncEnumerator twice.","messagePattern":"Enumerator is already running, does not allow call GetAsyncEnumerator twice\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/UniTask/Assets/Plugins/UniTask/Runtime/Channel.cs","lineNumber":388,"sourceCode":"                CancellationToken cancellationToken2;\n                CancellationTokenRegistration cancellationTokenRegistration1;\n                CancellationTokenRegistration cancellationTokenRegistration2;\n\n                T current;\n                bool cacheValue;\n                bool running;\n\n                public ReadAllAsyncEnumerable(SingleConsumerUnboundedChannelReader parent, CancellationToken cancellationToken)\n                {\n                    this.parent = parent;\n                    this.cancellationToken1 = cancellationToken;\n                }\n\n                public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)\n                {\n                    if (running)\n                    {\n                        throw new InvalidOperationException(\"Enumerator is already running, does not allow call GetAsyncEnumerator twice.\");\n                    }\n\n                    if (this.cancellationToken1 != cancellationToken)\n                    {\n                        this.cancellationToken2 = cancellationToken;\n                    }\n\n                    if (this.cancellationToken1.CanBeCanceled)\n                    {\n                        this.cancellationTokenRegistration1 =  this.cancellationToken1.RegisterWithoutCaptureExecutionContext(CancellationCallback1Delegate, this);\n                    }\n\n                    if (this.cancellationToken2.CanBeCanceled)\n                    {\n                        this.cancellationTokenRegistration2 = this.cancellationToken2.RegisterWithoutCaptureExecutionContext(CancellationCallback2Delegate, this);\n                    }\n\n                    running = true;","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/Cysharp/UniTask/blob/ceac8d6946b1125fe782cd171fbcb245b567dbf9/src/UniTask/Assets/Plugins/UniTask/Runtime/Channel.cs#L370-L406","documentation":"Thrown by SingleConsumerUnboundedChannel's ReadAllAsyncEnumerable when GetAsyncEnumerator is called a second time. The enumerable tracks a boolean 'running' flag set on first enumeration; a second call is rejected because the channel is designed for single-consumer use. Multiple concurrent enumerators would corrupt the shared queue.","triggerScenarios":"Calling 'await foreach' twice on the same IUniTaskAsyncEnumerable returned by reader.ReadAllAsync(). Or manually invoking GetAsyncEnumerator() twice on that enumerable. Or sharing the same single-consumer channel reader between two concurrent consumer tasks.","commonSituations":"Fan-out pattern incorrectly applied to a SingleConsumerUnboundedChannel. A test or utility that re-enumerates the same source. Code that breaks out of an await foreach loop and then tries to enumerate again.","solutions":["Use only one consumer per SingleConsumerUnboundedChannel — if multiple consumers are needed, create separate channels or implement a broadcast layer.","Create a new channel via Channel.CreateSingleConsumerUnbounded<T>() for each independent consumer.","If you need multi-consumer fan-out, subscribe each consumer to its own channel and forward items manually."],"exampleFix":"// before (throws on second consumer)\nvar enumerable = channel.Reader.ReadAllAsync();\nawait foreach (var x in enumerable) { }\nawait foreach (var y in enumerable) { } // throws\n\n// after\nvar ch1 = Channel.CreateSingleConsumerUnbounded<int>();\nvar ch2 = Channel.CreateSingleConsumerUnbounded<int>();\n// producer writes to both\nawait foreach (var x in ch1.Reader.ReadAllAsync()) { }\nawait foreach (var y in ch2.Reader.ReadAllAsync()) { }","handlingStrategy":"validation","validationCode":"// Ensure only one consumer per channel\n// Before enumerating, verify the channel is a fresh single-consumer channel\n// and you are the sole consumer.\n// There is no public IsEnumerating flag — design your code so each\n// SingleConsumerUnboundedChannel has exactly one ReadAllAsync caller.","typeGuard":null,"tryCatchPattern":"try\n{\n    await foreach (var item in channel.Reader.ReadAllAsync()) { /* ... */ }\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"GetAsyncEnumerator twice\"))\n{\n    // A second consumer attempted enumeration; redesign for single consumer\n}","preventionTips":["Document and enforce that SingleConsumerUnboundedChannel has exactly one consumer.","For fan-out scenarios, create separate channels per consumer rather than sharing one.","Avoid breaking out of an await foreach and re-entering the same enumerable."],"tags":["channel","async","single-consumer","enumeration"],"backgroundTag":null,"analyzedSha":"ceac8d6946b1125fe782cd171fbcb245b567dbf9","analyzedAt":"2026-08-13T19:16:39.025Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}