Open
Description
Issue description
The documentation for ChannelWriter<T>.(TryWrite|WaitToWriteAsync|WriteAsync)
does not say what happens in case the writer has been completed. In fact WaitToWriteAsync
throws the exception set in ChannelWriter.Complete(Exception)
if specified while returing false
if no exception was specified. In contrast WriteAsync
always throws a System.Threading.Channels.ChannelClosedException, optionally with the supplied exception as its InnerException. Neither of these are documented as throwing any exceptions.
The following program generates the same output on .NET 6.0 and .NET Framework 4.8
Test program source
using System;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace ChannelTest;
public class Program
{
private static string GetExceptionDescriptionWithInnerExceptions(Exception e)
{
if (e.InnerException != null)
return $"{e.GetType().FullName}: {e.Message} ---> {GetExceptionDescriptionWithInnerExceptions(e.InnerException)}";
else
return $"{e.GetType().FullName}: {e.Message}";
}
private static void PrintResultOrError(string op, Func<Task> f)
=> PrintResultOrError(op, async () => { await f(); return "Success"; });
private static void PrintResultOrError<T>(string op, Func<T> f)
=> PrintResultOrError(op, () => Task.FromResult(f()));
private static void PrintResultOrError<T>(string op, Func<Task<T>> f)
{
try
{
T result = f().GetAwaiter().GetResult();
Console.WriteLine($"{op}: {result}");
}
catch (Exception e)
{
Console.WriteLine($"{op} failed: {GetExceptionDescriptionWithInnerExceptions(e)}");
}
}
public static void Main(string[] args)
{
Console.WriteLine("== Write after complete, unbounded without exception ==");
TestChannelWriteAfterCompleted(false, null);
Console.WriteLine();
Console.WriteLine("== Write after complete, unbounded with exception ==");
TestChannelWriteAfterCompleted(false, new Exception("Foobar!"));
Console.WriteLine("== Write after complete, bounded without exception ==");
TestChannelWriteAfterCompleted(true, null);
Console.WriteLine();
Console.WriteLine("== Write after complete, bounded with exception ==");
TestChannelWriteAfterCompleted(true, new Exception("Foobar!"));
}
private static void TestChannelWriteAfterCompleted(bool bounded, Exception? exception)
{
Channel<string> channel = bounded ?
Channel.CreateBounded<string>(new BoundedChannelOptions(5) { FullMode = BoundedChannelFullMode.Wait, SingleReader = true, SingleWriter = true }) :
Channel.CreateUnbounded<string>(new UnboundedChannelOptions { SingleReader = true, SingleWriter = true });
channel.Writer.Complete(exception);
PrintResultOrError("TryWrite", () => channel.Writer.TryWrite("Hello, World!"));
PrintResultOrError("WaitToWriteAsync", () => channel.Writer.WaitToWriteAsync().AsTask());
PrintResultOrError("WriteAsync", () => channel.Writer.WriteAsync("World").AsTask());
}
}
Test program output
== Write after complete, unbounded with exception ==
TryWrite: False
WaitToWriteAsync failed: System.Exception: Foobar!
WriteAsync failed: System.Threading.Channels.ChannelClosedException: The channel has been closed. ---> System.Exception: Foobar!
== Write after complete, bounded without exception ==
TryWrite: False
WaitToWriteAsync: False
WriteAsync failed: System.Threading.Channels.ChannelClosedException: The channel has been closed.
== Write after complete, bounded with exception ==
TryWrite: False
5D3B
span>
WaitToWriteAsync failed: System.Exception: Foobar!
WriteAsync failed: System.Threading.Channels.ChannelClosedException: The channel has been closed. ---> System.Exception: Foobar!
Documentation source: https://github.com/dotnet/dotnet-api-docs/blob/main/xml/System.Threading.Channels/ChannelWriter%601.xml