{"record":{"id":"e4404f3e65641a11","repo":"Cysharp/UniTask","slug":"channel-is-already-closed","errorCode":null,"errorMessage":"Channel is already closed.","messagePattern":"Channel is already closed\\.","errorType":"exception","errorClass":"ChannelClosedException","httpStatus":null,"severity":"error","filePath":"src/UniTask/Assets/Plugins/UniTask/Runtime/Channel.cs","lineNumber":55,"sourceCode":"            if (this.TryRead(out var item))\n            {\n                return UniTask.FromResult(item);\n            }\n\n            return ReadAsyncCore(cancellationToken);\n        }\n\n        async UniTask<T> ReadAsyncCore(CancellationToken cancellationToken = default(CancellationToken))\n        {\n            if (await WaitToReadAsync(cancellationToken))\n            {\n                if (TryRead(out var item))\n                {\n                    return item;\n                }\n            }\n\n            throw new ChannelClosedException();\n        }\n\n        public abstract IUniTaskAsyncEnumerable<T> ReadAllAsync(CancellationToken cancellationToken = default(CancellationToken));\n    }\n\n    public abstract class ChannelWriter<T>\n    {\n        public abstract bool TryWrite(T item);\n        public abstract bool TryComplete(Exception error = null);\n\n        public void Complete(Exception error = null)\n        {\n            if (!TryComplete(error))\n            {\n                throw new ChannelClosedException();\n            }\n        }\n    }","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/Cysharp/UniTask/blob/ceac8d6946b1125fe782cd171fbcb245b567dbf9/src/UniTask/Assets/Plugins/UniTask/Runtime/Channel.cs#L37-L73","documentation":"Thrown by ReadAsyncCore when a consumer calls ReadAsync on a channel that is already completed and empty. After WaitToReadAsync returns false (signalling the channel is done) or returns true but TryRead finds nothing (items were drained between signal and read), the method has no data to return and throws ChannelClosedException. This mirrors the semantics of System.Threading.Channels.","triggerScenarios":"Calling ChannelReader<T>.ReadAsync() on a channel whose writer has already called Complete() and whose queue is empty. Also fires in a race where the channel completes between WaitToReadAsync signalling data-available and the subsequent TryRead.","commonSituations":"A producer calls Writer.Complete() while a consumer is mid-await on ReadAsync. A consumer reads items in a loop and the final ReadAsync hits the closed channel. Shutdown logic completes the channel before the consumer drains remaining items.","solutions":["Use ReadAllAsync (await foreach) instead of manual ReadAsync loops — it gracefully ends enumeration when the channel completes.","Before calling ReadAsync, check reader.Completion.IsCompleted and reader.TryRead to avoid hitting the closed-channel path.","Ensure the producer does not call Complete() until the consumer has finished consuming, or coordinate via a sentinel value.","Wrap ReadAsync in try-catch for ChannelClosedException if completion is expected mid-read."],"exampleFix":"// before\nvar item = await channel.Reader.ReadAsync(); // throws if channel completed\n\n// after\nif (channel.Reader.TryRead(out var item))\n{\n    // use item\n}\nelse if (await channel.Reader.Completion is { IsFaulted: false })\n{\n    // channel completed normally, nothing to read\n}","handlingStrategy":"try-catch","validationCode":"// Check completion state before ReadAsync\nif (channel.Reader.Completion.IsCompleted)\n{\n    // Channel is closed; do not call ReadAsync\n    return;\n}\nif (channel.Reader.TryRead(out var item))\n{\n    // got item safely\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    var item = await channel.Reader.ReadAsync(cancellationToken);\n}\ncatch (ChannelClosedException)\n{\n    // Channel was completed; expected during shutdown\n    break;\n}","preventionTips":["Prefer ReadAllAsync (await foreach) over manual ReadAsync loops — it terminates cleanly on channel completion.","Coordinate producer/consumer lifecycle so the channel is not completed while a consumer is mid-read.","Use a sentinel item to signal end-of-stream rather than relying on completion exceptions."],"tags":["channel","async","concurrency","producer-consumer"],"backgroundTag":null,"analyzedSha":"ceac8d6946b1125fe782cd171fbcb245b567dbf9","analyzedAt":"2026-08-13T19:16:39.025Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}