主窗体激活keydown事件,但是阻塞了主线程

在.netcore6中使用keydown方法对键盘进行监听,但是要是一直按着某个按键就会阻塞我的主线程

  public async void KeyDownAction(object? sender, KeyEventArgs e)
        {
            
                if (e.KeyCode == Keys.Space || e.KeyCode == Keys.W || e.KeyCode == Keys.D || e.KeyCode == Keys.A || e.KeyCode == Keys.S)
                {

                    switch (e.KeyCode)
                    {
                        case Keys.Space:
                            this.timer1.Stop(); break;
                        case Keys.W:
                            if (this._snake.direction == Direction.DOWN) break;
                            this._snake.direction = Direction.UP; break;
                        case Keys.S:
                            if (this._snake.direction == Direction.UP) break; this._snake.direction = Direction.DOWN; break;
                        case Keys.A: if (this._snake.direction == Direction.RIGHT) break; this._snake.direction = Direction.LEFT; break;
                        case Keys.D: if (this._snake.direction == Direction.LEFT) break; this._snake.direction = Direction.RIGHT; break;
                    }
                }
           
           

        }

这段代码旨在改变蛇的行进方向

在这段代码中,KeyDown事件的处理器中有一个问题,那就是如果一直按住某个键,就会频繁触发KeyDown事件,导致主线程被阻塞。

要解决这个问题,可以采取以下措施:

一、使用一个Timer在KeyDown事件后延迟一段时间再执行逻辑,比如:

 Timer timer = null;
 void KeyDownAction(object sender, KeyEventArgs e) 
 {
     if (timer != null) timer.Dispose();
     timer = new Timer(Delay); 
     timer.Elapsed += (s, ea) => 
     {
         // 键盘逻辑处理
        ...
     };
 }
 
 private int Delay = 200;  // 延迟时间ms

这样可以避免在按键期间过快触发逻辑处理。

二、使用异步方法并await Task.Delay延迟处理:

async void KeyDownAction(object sender, KeyEventArgs e) 
{
    await Task.Delay(200);  // 延迟200ms
    // 键盘逻辑处理
   ... 
}

三、使用System.Threading.Timer启动一个后台线程定期检查键盘状态,这样主线程就不会被阻塞:

Timer timer = null;
void KeyDownAction(object sender, KeyEventArgs e)
{
    timer = new Timer(Checkkeyboard); 
    timer.Change(0, 50); // 每50ms检查一次
}

private void CheckKeyboard(object state)
{
    if (Keyboard.IsKeyDown(Key.Space)) { /* Do Something */ } 
}

四、使用Observable.Interval在Rx中周期性检查按键状态:

 var keyObservable = Observable.Interval(TimeSpan.FromMilliseconds(50))
                                .Subscribe(x => { 
                                    if (Keyboard.IsKeyDown(Key.Space)) { /* Do Something */ } 
                                });

以上就是几种解决方案,总的来说就是采用定时器或线程来周期检查键盘状态,而不是直接在KeyDown事件中执行所有的逻辑,这可以避免主线程被大量的键盘事件阻塞。

尝试一下这些方案,或许能够帮你解决问题。

建议每个按键的逻辑分开写,不要写在一起

简单处理 就是加个lock锁,代码上看也没有线程异步,就是同步方法。所以加个lock锁让他们排队即可解决

可以添加时间间隔,降低调用的频率来提高程序的响应效率。可以设定连着按时每秒触发10次,10此以内忽略掉。参考如下代码:

        private void KeyDownAction(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Space || e.KeyCode == Keys.W || e.KeyCode == Keys.D || e.KeyCode == Keys.A || e.KeyCode == Keys.S)
            {
                if (DateTime.Now.Ticks - last < 1000000)//100ms以内忽略
                    return;
                last = DateTime.Now.Ticks;

                switch (e.KeyCode)
                {
                    case Keys.Space:
                        this.timer1.Stop(); break;
                    case Keys.W:
                        if (this._snake.direction == Direction.DOWN) break;
                        this._snake.direction = Direction.UP; break;
                    case Keys.S:
                        if (this._snake.direction == Direction.UP) break; this._snake.direction = Direction.DOWN; break;
                    case Keys.A: if (this._snake.direction == Direction.RIGHT) break; this._snake.direction = Direction.LEFT; break;
                    case Keys.D: if (this._snake.direction == Direction.LEFT) break; this._snake.direction = Direction.RIGHT; break;
                }
            }

        }


在窗体内添加一个long变量记录上次运行的tick。

        private long last;


将此textView的宽高设置成固定大小,这样只会更新文字

试试使用Task.Run方法将处理逻辑放到后台线程执行,避免阻塞主线程。

public async void KeyDownAction(object? sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Space || e.KeyCode == Keys.W || e.KeyCode == Keys.D || e.KeyCode == Keys.A || e.KeyCode == Keys.S)
    {
        await Task.Run(() =>
        {
            switch (e.KeyCode)
            {
                case Keys.Space:
                    this.timer1.Stop(); break;
                case Keys.W:
                    if (this._snake.direction == Direction.DOWN) break;
                    this._snake.direction = Direction.UP; break;
                case Keys.S:
                    if (this._snake.direction == Direction.UP) break; this._snake.direction = Direction.DOWN; break;
                case Keys.A: if (this._snake.direction == Direction.RIGHT) break; this._snake.direction = Direction.LEFT; break;
                case Keys.D: if (this._snake.direction == Direction.LEFT) break; this._snake.direction = Direction.RIGHT; break;
            }
        });
    }
}

参考gpt:
在你的代码中,你使用了async void来定义KeyDownAction方法,但是在事件处理程序中使用async void是不推荐的做法,因为它可能导致无法处理异常或取消操作。此外,你也提到在一直按住某个按键时会阻塞主线程,这是因为KeyDown事件在按键按下时触发,而不是按住期间持续触发。

如果你希望在按键按下期间持续触发事件,可以使用KeyUp事件来监听键盘释放的动作,然后根据按键状态来判断蛇的行进方向。下面是一个使用KeyUp事件的示例代码:

private Dictionary<Keys, bool> keyState = new Dictionary<Keys, bool>();

public MainForm()
{
    InitializeComponent();
    this.KeyPreview = true;
    this.KeyUp += KeyUpAction;
    this.KeyDown += KeyDownAction;
}

private void KeyDownAction(object? sender, KeyEventArgs e)
{
    keyState[e.KeyCode] = true;
    HandleKeyState();
}

private void KeyUpAction(object? sender, KeyEventArgs e)
{
    keyState[e.KeyCode] = false;
    HandleKeyState();
}

private void HandleKeyState()
{
    if (keyState.ContainsKey(Keys.Space) && keyState[Keys.Space])
    {
        this.timer1.Stop();
    }
    else if (keyState.ContainsKey(Keys.W) && keyState[Keys.W])
    {
        if (this._snake.direction != Direction.DOWN)
            this._snake.direction = Direction.UP;
    }
    else if (keyState.ContainsKey(Keys.S) && keyState[Keys.S])
    {
        if (this._snake.direction != Direction.UP)
            this._snake.direction = Direction.DOWN;
    }
    else if (keyState.ContainsKey(Keys.A) && keyState[Keys.A])
    {
        if (this._snake.direction != Direction.RIGHT)
            this._snake.direction = Direction.LEFT;
    }
    else if (keyState.ContainsKey(Keys.D) && keyState[Keys.D])
    {
        if (this._snake.direction != Direction.LEFT)
            this._snake.direction = Direction.RIGHT;
    }
}

在这个示例中,我们使用keyState字典来保存按键的状态,true表示按键按下,false表示按键释放。当键盘的按键状态发生改变时,调用HandleKeyState方法来根据按键状态来处理蛇的行进方向。

通过这种方式,即使用户按住某个按键,也可以在HandleKeyState方法中根据按键状态连续更新蛇的行进方向,而不会阻塞主线程。