AsyncTaskMethodBuilder error when ending DialogueManager dialogue

Godot Version

v4.4.rc3.mono.official [15ff45068]

Question

So I’ve been refactoring my gdscript code for the last few days to C#. I have some experience with C# development but it’s a little bit different in Godot.

I mostly have issues with understanding and implementing asynchronous methods and signals.

I have this bug which happens when the dialogue ends in DialogueManager plugin, and it happens when I go through dialogue fast, like mashing the Enter key.

I understand that it’s a NullReferenceException because something is null, but I can’t figure out what.

Error:

E 0:00:13:857   void System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1.MoveNext(System.Threading.Thread): System.NullReferenceException: Object reference not set to an instance of an object.
  <C++ Error>   System.NullReferenceException
  <C++ Source>  :0 @ void System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1.MoveNext(System.Threading.Thread)
  <Stack Trace> :0 @ void System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1.MoveNext(System.Threading.Thread)
                SignalAwaiter.cs:58 @ void Godot.SignalAwaiter.SignalCallback(nint, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_bool*)

Relevant code:

InteractionManager.cs(instantiated under Globals autoload)

public partial class InteractionManager : Node
{
    public Player? Player { get; set; }
    private List<InteractComponent>? _activeAreas;
    private bool _canInteract;
    

    public override void _Ready()
    {
        Player = GetTree().GetFirstNodeInGroup("player") as Player;
        _activeAreas = new List<InteractComponent>();
        _canInteract = true;
        Global.InteractionManager = this;
    }

    public override void _PhysicsProcess(double delta)
    {
        if (_activeAreas?.Count > 0 && _canInteract)
        {
            _activeAreas = _activeAreas.OrderBy(area => Player?.GlobalPosition.DistanceTo(area.GlobalPosition)).ToList();
        }
    }

    public async override void _Input(InputEvent @event)
    {
        if (@event.IsActionPressed("interact") && _canInteract)
        {
            GD.Print("Interact Pressed");
            if (_activeAreas?.Count > 0)
            {
                _activeAreas[0].EmitInteractionStarted();
                _canInteract = false;
                await ToSignal(_activeAreas[0], InteractComponent.SignalName.InteractionEnded);
                _canInteract = true;
            }
        }
    }

    public void RegisterArea(InteractComponent area)
    {
        _activeAreas?.Add(area);
    }
    
    public void UnregisterArea(InteractComponent area)
    {
        _activeAreas?.Remove(area);
    }
    
}

InteractComponent.cs (instantiated under every “interactable”)

public partial class InteractComponent : Area3D
{
    [Signal]
    public delegate void InteractionStartedEventHandler();
    [Signal]
    public delegate void InteractionEndedEventHandler();

    public override void _Ready()
    {
        BodyEntered += OnBodyEntered;
        BodyExited += OnBodyExited;
    }

    public void EmitInteractionStarted()
    {
        EmitSignal(nameof(InteractionStarted));
    }
    public void EmitInteractionEnded()
    {
        EmitSignal(nameof(InteractionEnded));
    }
    
    private void OnBodyEntered(Node3D body)
    {
        if (body is Player) Global.InteractionManager.RegisterArea(this);
    }
    
    private void OnBodyExited(Node3D body)
    {
        if (body is Player) Global.InteractionManager.UnregisterArea(this);
    }
}

TestNpc.cs (has InteractComponent instantiated under it)

public partial class TestNpc : CsgBox3D
{
	private InteractComponent? _interactComponent;
	public override void _Ready()
	{
		_interactComponent = GetNode<InteractComponent>("InteractComponent");
		_interactComponent.InteractionStarted += async () => await OnInteract();
	}

	private async Task OnInteract()
	{
		Resource dialogue = GD.Load<Resource>("res://assets/dialogue/test_dialogue.dialogue");
		DialogueManager.ShowExampleDialogueBalloon(dialogue, "start");
		await ToSignal(DialogueManager.Instance, "dialogue_ended");
		_interactComponent?.EmitInteractionEnded();
	}
}