runtime: Channels can easily have unobserved exceptions
Description
A user reported an issue where there was an UnobservedTaskException
in code that used a Channel
. The problem was that the Exception
was observed via Channel.Reader.WaitToReadAsync()
, but the same exception is also set on a TaskCompletionSource
that is visible via Channel.Reader.Completion
. So proper code needs to know that it has to observe the exception twice to avoid an UnobservedTaskException
.
Reproduction Steps
Original code that was problematic.
TaskScheduler.UnobservedTaskException += (sender, ex) =>
{
Console.WriteLine(ex.Exception);
};
await Func();
GC.Collect();
Console.ReadKey();
static async Task Func()
{
var channel = Channel.CreateBounded<int>(2);
channel.Writer.Complete(new Exception());
try
{
while (await channel.Reader.WaitToReadAsync().ConfigureAwait(false)) // throws
{
while (channel.Reader.TryRead(out var item))
{
}
}
// Observe any errors in the completion task
await channel.Reader.Completion;
// We tried to be good citizens and observe the Task, but even this seems like a stretch for consumers to know that they should do
}
catch { }
}
The fix would be to move await channel.Reader.Completion;
into a finally, and to wrap it in another try catch.
Expected behavior
Expected no UnobservedTaskException
if we observed the exception from one of the read APIs.
At a bare minimum the Channel docs should show using the Reader.Completion
property properly.
One suggestion was to make Reader.Completion
lazy, it looks like that might be possible since we set the _parent._doneWriting
exception property everywhere that we complete the Reader.Completion
TCS currently. This would make it so you don’t need to observe the Reader.Completion
task unless you already grabbed the property.
Actual behavior
UnobservedTaskException
due to not observing Reader.Completion
.
Regression?
No.
Known Workarounds
Double observe the exception by moving await Reader.Completion
into a finally
block.
Configuration
No response
Other information
No response
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 2
- Comments: 28 (27 by maintainers)
That’s “unhandled”, not “unobserved”. UnobservedTaskException doesn’t show up anywhere in dotnet/wpf. https://github.com/dotnet/wpf/search?q=UnobservedTaskException
The original scenario, when you never read the Completion task, but you use ReadAllAsync or ReadAsync or WaitToReadAsync. If the exception is observed in a reading loop with one of those APIs, there shouldn’t be an unobserved task exception.
That’s because it happens to access the Task’s Exception as an implementation detail. Change your example to ActionBlock, for example, and I expect you will see an exception.