TaskCompletionSource no funciona con más de una tarea esperada: ¿qué estoy haciendo mal?
Estoy trabajando en un juego usando el motor de juego Godot con Mono/C#.
Estoy tratando de lograr lo siguiente:
- Mostrar un mensaje en la pantalla
- Esperar a la pulsación de un botón del ratón/toque de pantalla
- Mostrar otro mensaje
- Esperar a la pulsación de nuevo
- …
Por lo tanto, tengo un método Say()
:
csharp
async Task Say(string msg)
{
SetStatusText(msg);
_tcs = new TaskCompletionSource<Vector2>();
await _tcs.Task;
SetStatusText(string.Empty);
}
Lo que espero que suceda es lo siguiente:
csharp
async Task Foo()
{
// Muestra "First".
await Say("First");
// "Second" debería mostrarse después de una pulsación.
await Say("Second");
// "Third" debería mostrarse después de otra pulsación.
await Say("Third");
}
Lo que realmente sucede es lo siguiente:
- Se muestra “First”.
- “Second” se muestra después de una pulsación.
- “Third” nunca aparece, incluso después de una pulsación.
Lo rastreé hasta que _tcs
es null
(o está en un estado inválido, si no lo establezco en null
) en mi código de pulsación de botón del ratón:
public void OnMouseButtonClicked(Vector2 mousePos)
{
if(_tcs != null)
{
_tcs.SetResult(mousePos);
_tcs = null;
return;
}
// Otro código, que se ejecuta si no se utiliza _tcs.
}
El código de pulsación de botón del ratón establece el resultado de _tcs
y esto funciona bien para el primer await
, pero luego falla, aunque estoy creando una nueva instancia de TaskCompletionSource
en cada llamada de Say()
.
¿Es un problema de Godot o mi conocimiento de async en C# se ha vuelto tan oxidado que me estoy perdiendo algo aquí? Casi parece como si _tcs
se estuviera capturando y reutilizando.
davy.ai
Es posible que
_tcs
esté siendo capturado y reutilizado, lo que está causando un comportamiento inesperado. Una manera de evitar esto es crear una nueva instancia de_tcs
cada vez que se llama aSay()
, tal como ya estás haciendo. Sin embargo, también es importante asegurarse de que el controlador de clic no se llame varias veces, lo que podría hacer queTaskCompletionSource
se establezca varias veces y produzca resultados inesperados.Una posible solución es registrar el controlador de clic solo una vez y luego eliminarlo una vez que se haya llamado. Esto se puede lograr utilizando el método
Input.SetMouseMode()
, que te permite capturar la entrada del mouse y luego liberarla una vez que se haya gestionado el clic. Aquí tienes un ejemplo de implementación:En esta implementación, el controlador de clic solo se registra cuando se llama a
Say()
, y se captura la entrada del mouse utilizandoInput.SetMouseMode()
. Una vez que se llama al controlador de clic, se estableceTaskCompletionSource
y se libera la entrada del mouse utilizandoInput.SetMouseMode()
. Esto asegura que el controlador de clic solo se llamará una vez y se logrará el comportamiento correcto.