Description
Description
Code execution stops unexpectedly under some async code.
Reproduction Steps
Taking this code into consideration
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
async static Task Main()
{
var bugged = true;
if (bugged)
await Use();
else
Use();
while (true) Console.ReadKey();
}
public static async Task Use()
{
var coordinator = new TaskCoordinator();
coordinator.Register(Task1);
async Task Task1(TaskCoordinator.Handle handle)
{
await Task.Delay(200);
throw new NotImplementedException();
Program.Print("Task 1 Finished");
}
coordinator.Register(Task2);
async Task Task2(TaskCoordinator.Handle handle)
{
await Task.Delay(400);
handle.ThrowIfCancellationRequested();
Program.Print("Task 2 Finished");
}
coordinator.Register(Task3);
async Task Task3(TaskCoordinator.Handle handle)
{
await Task.Delay(400);
handle.ThrowIfCancellationRequested();
Program.Print("Task 3 Finished");
}
try
{
await coordinator.Await();
}
catch (Exception ex)
{
Program.Print(ex);
return;
}
Program.Print("Done");
}
public static void Print(object target) => Console.WriteLine(target);
}
class TaskCoordinator
{
CancellationTokenSource CancellationSource;
public CancellationToken CancellationToken => CancellationSource.Token;
TaskCompletionSource<bool> StartSource;
TaskCompletionSource<bool> ProcessingSource;
volatile int HandleCount;
public Handle CreateHandle()
{
Interlocked.Increment(ref HandleCount);
return new Handle(this);
}
public async Task Await()
{
StartSource.SetResult(true);
await ProcessingSource.Task;
}
void CompleteHandle()
{
if (Interlocked.Decrement(ref HandleCount) is not 0)
return;
ProcessingSource.TrySetResult(true);
}
void FailHandle(Exception exception)
{
try
{
Program.Print("Previous Line");
var output = ProcessingSource.TrySetException(exception);
//Anything bellow here doesn't execute
Program.Print("Next Line");
if (output is false)
return;
CancellationSource.Cancel();
}
catch (Exception)
{
Program.Print("Catch Exception");
}
finally
{
Program.Print("Finally Block");
}
}
public void Register(Func<Handle, Task> function)
{
var handle = CreateHandle();
var task = function(handle);
Register(task);
}
public async void Register(Task task)
{
try
{
await StartSource.Task;
await task;
CompleteHandle();
}
catch (OperationCanceledException) when (CancellationToken.IsCancellationRequested)
{
//Do Nothing
}
catch (Exception ex)
{
FailHandle(ex);
}
}
public TaskCoordinator()
{
CancellationSource = new CancellationTokenSource();
StartSource = new();
ProcessingSource = new();
}
public struct Handle
{
readonly TaskCoordinator Coordinator;
public CancellationToken CancellationToken => Coordinator.CancellationToken;
public bool IsCancellationRequested => CancellationToken.IsCancellationRequested;
public void ThrowIfCancellationRequested() => CancellationToken.ThrowIfCancellationRequested();
public void Fail()
{
var exception = new Exception($"Handle Failed");
Fail(exception);
}
public void Fail(Exception exception)
{
Coordinator.FailHandle(exception);
}
public Handle(TaskCoordinator Coordinator)
{
this.Coordinator = Coordinator;
}
}
}
The problem occurs inside the method TaskCoordinator.FailHandle
, the issue is rather strange, simply put; any code after the ProcessingSource.TrySetException(exception)
line doesn't work, including code inside the catch and finally blocks.
This only occurs when the bugged
flag is set to true inside the Program.Main
function.
The bugged flag will only change the behaviour of using the Program.Use
method; the bug only occurs when the returned task is awaited, and it doesn't occur when the task is forgotten. I'm not sure if this is tied directly to the bug, but it's just an observation I made.
Even breakpoints on any line after ProcessingSource.TrySetException(exception)
do not work.
Expected behavior
Code inside TaskCoordinator.FailHandle
runs normally, respecting the try-catch-finally blocks.
Actual behavior
Execution of TaskCoordinator.FailHandle
stops at ProcessingSource.TrySetException(exception)
when the bugged flag is set to true inside of 'Program.Main'
Regression?
The code was originally made to be used in the Unity game engine, but I was first prototyping it in an empty .NET project.
The code is bugged using .NET 9 & .NET 8.
But it works as expected under Unity, which uses some version of Mono, of course.
I also tested the code on dotnetfiddle.net to ensure that it isn't just my dotnet installation that is broken, and yes, it's still bugged.
Known Workarounds
None.
Configuration
.NET: 9.0.203
OS: Windows 11 23H2
Arch: x64
I'm unsure if it's specific to this configuration
Other information
No response