-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Open
Labels
area-System.Threading.ChannelsuntriagedNew issue has not been triaged by the area ownerNew issue has not been triaged by the area owner
Description
Description
Description:
I encountered a scenario where ChannelReader.Completion never becomes IsCompleted == true, even though:
- All items in the channel have been successfully read using TryRead.
channel.Writer.TryComplete()has been called.
This causes code that awaitschannel.Reader.Completionor checksIsCompletedto hang indefinitely.
Reproduction Steps
using System;
using System.Threading.Channels;
using System.Threading.Tasks;
public class Program
{
public static async Task Main()
{
while (true)
{
var channel = Channel.CreateUnbounded<int>(new UnboundedChannelOptions
{
SingleWriter = false,
SingleReader = false,
AllowSynchronousContinuations = false
});
await channel.Writer.WriteAsync(1);
await channel.Writer.WriteAsync(2);
channel.Reader.TryRead(out _);
var t1 = Task.Run(() =>
{
channel.Reader.TryRead(out _);
});
var t2 = Task.Run(() =>
{
channel.Writer.TryComplete();
});
await Task.WhenAll(t1, t2);
channel.Reader.TryRead(out _);
if (!channel.Reader.Completion.IsCompleted)
{
while (true)
{
channel.Writer.TryComplete();
channel.Reader.TryRead(out _);
Console.WriteLine(channel.Reader.Completion.IsCompleted);
await Task.Delay(1000);
}
}
}
}
}Expected behavior
channel.Reader.Completion.IsCompleted should become true after:
- All items have been read.
channel.Writer.TryComplete()has been called.
Actual behavior
channel.Reader.Completion.IsCompleted stays false indefinitely.
Regression?
No response
Known Workarounds
No response
Configuration
- net 10 (sdk 10.0.100)
- macOS Version 15.6.1
- ARM64
- not specific
Other information
Looking at the internal implementations of TryRead in different channel types.
UnboundedChannel
public override bool TryRead([MaybeNullWhen(false)] out T item)
{
UnboundedChannel<T> parent = this._parent;
if (parent._items.TryDequeue(out item))
{
UnboundedChannel<T>.UnboundedChannelReader.CompleteIfDone(parent);
return true;
}
item = default;
return false;
}- TryRead in UnboundedChannel does not acquire any lock when dequeuing items.
- The CompleteIfDone method may be invoked concurrently, and internal state updates may not be fully synchronized in multithreaded scenarios.
BoundedChannel
public override bool TryRead([MaybeNullWhen(false)] out T item)
{
BoundedChannel<T> parent = this._parent;
lock (parent.SyncObj)
{
if (!parent._items.IsEmpty)
{
item = this.DequeueItemAndPostProcess();
return true;
}
}
item = default;
return false;
}- In contrast, BoundedChannel.TryRead uses a lock (SyncObj) to ensure that the dequeue operation and state updates are fully synchronized.
- The issue with Completion never completing does not occur with BoundedChannel, even under the same concurrent usage patterns.
Metadata
Metadata
Assignees
Labels
area-System.Threading.ChannelsuntriagedNew issue has not been triaged by the area ownerNew issue has not been triaged by the area owner