es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

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.

Tags:  , ,

Answer

  1. Avatar for 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 a Say(), 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 que TaskCompletionSource 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:

    using Godot;
    using System.Threading.Tasks;
    
    public class Example : Node
    {
        private TaskCompletionSource<vector2> _tcs;
    
        async Task Say(string msg)
        {
            SetStatusText(msg);
            _tcs = new TaskCompletionSource<vector2>();
            Input.SetMouseMode(Input.MouseMode.Captured);
            await _tcs.Task;
            Input.SetMouseMode(Input.MouseMode.Visible);
            SetStatusText(string.Empty);
        }
    
        public void OnMouseButtonClicked(Vector2 mousePos)
        {
            if (_tcs != null)
            {
                _tcs.SetResult(mousePos);
                _tcs = null;
            }
        }
    
        async Task Foo()
        {
            // Muestra "First".
            await Say("First");
            // "Second" debería mostrarse después de un clic.
            await Say("Second");
            // "Third" debería mostrarse después de otro clic.
            await Say("Third");
        }
    }
    

    En esta implementación, el controlador de clic solo se registra cuando se llama a Say(), y se captura la entrada del mouse utilizando Input.SetMouseMode(). Una vez que se llama al controlador de clic, se establece TaskCompletionSource y se libera la entrada del mouse utilizando Input.SetMouseMode(). Esto asegura que el controlador de clic solo se llamará una vez y se logrará el comportamiento correcto.

Comments are closed.