You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForms中使用Task.Run导致代码执行终止问题求助

问题描述

我代码有点乱,正试着不靠谷歌自己解决问题。我在OnKeyDown方法里实现了屏幕上方块向右移动的逻辑,想要用户按下A键时停止右移并切换为左移。我尝试用Task.Run监听其他输入,检测到按键就返回true跳出循环再调用左移方法,但这么做直接导致代码执行停止,现在卡壳了,不知道还有什么可行方案。

以下是全部相关代码(另有绘制网格方法和OnPaint方法):

构造函数

public Form1()
{
    SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
    InitializeComponent();
}

Form加载事件

private void Form1_Load(object sender, EventArgs e)
{
    FormBorderStyle = FormBorderStyle.FixedSingle;
    this.BackColor = Color.FromArgb(221, 221, 221);
    this.CenterToScreen();
    this.NumCells = (int)Math.Floor((float)this.ClientSize.Width / CellSize);
}

网格绘制方法

private void Form1_Paint(object sender, PaintEventArgs pe)
{ 
    Graphics g = pe.Graphics;

    for (int i = 1; i < NumCells; i++)
    {
        //Vertical Lines
        g.DrawLine(BlackPen, i * CellSize, 0, i * CellSize, NumCells * CellSize);

        //Horizontal Lines
        g.DrawLine(BlackPen, 0, i * CellSize, NumCells * CellSize, i * CellSize);
    }
}

重写OnPaint方法

protected override void OnPaint(PaintEventArgs pe)
{
    base.OnPaint(pe);

    Graphics g = pe.Graphics;

    Pen blackPen = new Pen(Color.Black, 20);

    if (this.Move == true)
    {
        var lpnt1 = new PointF(X * CellSize / 2, Y1 * 1);
        var lpnt2 = new PointF(X * CellSize / 2, Y2 * 1);
        g.DrawLine(blackPen, lpnt1, lpnt2);
    }

    Log(); //Custom `Debug.WriteLine` string to log current variables.
}

重写OnKeyDown方法

protected override void OnKeyDown(KeyEventArgs ke)
{
    base.OnKeyDown(ke);

    if (ke.KeyCode == Keys.Escape)
    {
        this.Close();
    }

    if (ke.KeyCode == Keys.D) 
    {
        this.Move = true;

        while (this.Move == true)
        {
            var t1 = Task.Run(() =>
            {
                if (ke.KeyCode == Keys.D)
                    return true;
                else return false;
            });

            if (t1.Result == true)
                break;

            if (X > this.NumCells * 2)
            {
                X = 1;
                this.Invalidate();
                this.Update();
                break;
            }

            Thread.Sleep(Velocity);

            X += MoveX;
            this.Invalidate();
            this.Update();
        }
    }
}
解决方案

你的问题核心是在UI线程里用死循环+Thread.Sleep阻塞了消息队列,导致后续按键事件根本无法被处理,而且Task.Run里的判断逻辑完全无效——ke是当前OnKeyDown的参数,只会是按下D键那次的KeyCode,不可能监听后续的A键。

正确的做法是用Timer控制移动逻辑,分离输入和执行动作:

1. 定义全局状态变量

private Timer _moveTimer;
private int _moveDirection = 0; // 0=停止, 1=右移, -1=左移
public int X = 1;
public int CellSize = 40; // 根据你的实际值调整
public int Velocity = 100; // 移动间隔(毫秒)

2. 初始化Timer(在Form构造函数中)

public Form1()
{
    SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
    InitializeComponent();
    _moveTimer = new Timer();
    _moveTimer.Interval = Velocity;
    _moveTimer.Tick += MoveTimer_Tick;
}

3. 实现Timer的Tick事件处理移动

private void MoveTimer_Tick(object sender, EventArgs e)
{
    X += _moveDirection;
    // 边界处理,超出范围重置位置
    if (X > NumCells * 2)
    {
        X = 1;
    }
    else if (X < 1)
    {
        X = NumCells * 2;
    }
    Invalidate(); // 触发窗体重绘
}

4. 修改OnKeyDown,只控制方向和Timer启停

protected override void OnKeyDown(KeyEventArgs ke)
{
    base.OnKeyDown(ke);

    if (ke.KeyCode == Keys.Escape)
    {
        Close();
        return;
    }

    switch(ke.KeyCode)
    {
        case Keys.D:
            _moveDirection = 1; // 设置右移方向
            _moveTimer.Start();
            break;
        case Keys.A:
            _moveDirection = -1; // 设置左移方向
            _moveTimer.Start();
            break;
        case Keys.S: // 可选:添加停止键
            _moveTimer.Stop();
            _moveDirection = 0;
            break;
    }
}

5. 优化OnPaint方法(避免Pen内存泄漏)

protected override void OnPaint(PaintEventArgs pe)
{
    base.OnPaint(pe);
    Graphics g = pe.Graphics;

    // 使用using自动释放Pen资源
    using(Pen blackPen = new Pen(Color.Black, 20))
    {
        if (_moveDirection != 0) // 有移动方向时绘制线条
        {
            // 这里的Y坐标根据你的实际需求调整,假设从顶部到底部
            var lpnt1 = new PointF(X * CellSize / 2, 0);
            var lpnt2 = new PointF(X * CellSize / 2, ClientSize.Height);
            g.DrawLine(blackPen, lpnt1, lpnt2);
        }
    }
    Log();
}

为什么之前的方案行不通?

  • OnKeyDown运行在UI线程,死循环+Thread.Sleep会直接卡死UI线程,消息队列被阻塞,后续按键事件根本无法被捕获。
  • Task.Run里的ke参数是按下D键时的实例,永远不会变化,所以判断逻辑完全没用。

用Timer的优势

  • Timer的Tick事件在UI线程触发,但不会阻塞消息队列,按键事件能正常响应。
  • 逻辑清晰:按键只负责切换状态,Timer负责执行移动动作,完全分离输入和业务逻辑。

内容的提问来源于stack exchange,提问作者ThisQRequiresASpecialist

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 14:55:14