c# - 如何在C#中使用鼠标允许异步控制?

标签 c# asynchronous task game-engine

我对C#还是个新手,对异步编程甚至还比较新。我正在使用Windows Forms从头开始制作一个简单的2D游戏(并在编写自己的游戏引擎的过程中),并且似乎对我的移动异步编程遇到了麻烦。尽管此代码是供游戏使用的,但我希望出于工作目的使用异步处理,这是我在业余时间开始对此进行编程的部分原因。最初,我的机芯是同步的并且运行完美,但是我希望能够在行程中间更改机芯的目的地,并且无法同步进行。我一直在尝试使用此处的各种文章和Microsoft官方文档将同步方法转换为异步方法,但到目前为止,运气还不如预期的那样。我尽力减少代码并尽可能地清理它,而不会掩盖问题。以下是我希望代码执行的操作的详细信息以及问题的摘要。

对代码有什么期望?

该代码的当前唯一意图是接受鼠标右键单击窗口,并将“单位”从其当前位置移至所单击的位置,同时仍然允许您在窗口中单击以根据需要更改位置(想想RTS风格的机芯)。

我一直遇到什么问题?

我只是对我的这段代码问题的根本原因有所猜测,但是症状是,在执行了移动方法之后,程序变慢了几次,直到最终冻结为止。我通过调试进行了测试,发现每次使用ram的速度都会缓慢上升,每次使用都会使CPU使用率显着提高,只需单击几下即可达到100%。机芯本身工作正常,但系统停滞不前,使其无法使用。

我为解决该问题做了什么?

我已将大多数运动代码重写为3倍,以使其正常工作。如果有人想看看这些版本之间的更改,我有一个完整的项目历史记录。我从使用会导致跨线程异常的后台工作程序过渡到实现当前任务的原始版本,该版本具有可处理的问题,并且取消过程最终无法实现此代码。我以为CPU和ram使用率的提高是由于任务处理不当而导致的,因此添加了dispose命令,但这似乎并没有太大帮助。我已经尽了最大的努力使它无法静态复制,但这没有效果。我已经独自解决这个问题了几个星期,却找不到解决我问题的答案。



摘要

以下是代码功能的快速摘要列表,包括已删除的代码。


创建“运动场”(占据整个窗口的空白白色表单)和单位(32px彩色正方形图片框)。
在点击“ Good Guy”后,将他设置为选中状态。
单击“运动场”后,将使用异步任务将所选单位移动到单击的位置。
完成任务后,处置不需要的资源。


调用鼠标单击事件及其接收方法

// Mouse click event for moving the "Good Guy" to the clicked location on the play field.

playField.MouseClick += (sender1, e1) => PlayFieldOnMouseClickAsync(e1, goodGuy, loopRunning);


// The receiving method for the mouse click event handler.
private static async void PlayFieldOnMouseClickAsync(MouseEventArgs e1
                                                   , Control playerUnit
                                                   , bool loopRunning) {
    switch (e1.Button) {    
// Right click only moves selected units.
        case MouseButtons.Right:
            if (!UnitSelected) return;

// The variables needed for moving units.
            var MoveDistance = new Point(MousePosition.X - playerUnit.Location.X, MousePosition.Y - playerUnit.Location.Y);
            var speedX            = 1;
            var speedY            = 1;
            var moveNegativeXFlag = false;
            var moveNegativeYFlag = false;
            var movementLoopTokenSource = new CancellationTokenSource();

// These two checks determine if the unit needs to be moved positively or negatively along the play field before converting the distance to its absolute value for easy math manipulation later.
            if (moveDistance.X < 0) {
                speedX            = -1;
                moveNegativeXFlag = true;
            }

            if (moveDistance.Y < 0) {
                speedY            = -1;
                moveNegativeYFlag = true;
            }

            moveDistance.X = Math.Abs(moveDistance.X);
            moveDistance.Y = Math.Abs(moveDistance.Y);

// If the loop is already running then this cancels the loop before starting a new loop.
            if (loopRunning) {
                movementLoopTokenSource.Cancel();
            }

// This toggles the state of running loop flag but may not work on subsequent runs if the loop doesn't finish.
            loopRunning = !loopRunning;
            var loopCancellationToken = movementLoopTokenSource.Token;

// Prepares the movement task to be called.
            var movementLoop = MovementLoopTask(loopCancellationToken
                                              , speedX   
                                              , speedY
                                              , moveNegativeXFlag
                                              , moveNegativeYFlag
                                              , playerUnit
                                              , moveDistance);

// Starts the movement loop and upon finish sets the flag back and disposes unneeded items.
            await movementLoop;
            loopRunning = false;
            movementLoop.Dispose();
            movementLoopTokenSource.Dispose();
            break;
    }
}


运动执行任务

// After being called this asynchronously performs movement for the selected unit.
public static async Task MovementLoopTask(
                                          CancellationToken loopCancellationToken
                                        , int               speedX
                                        , int               speedY
                                        , bool              moveNegativeXFlag
                                        , bool              moveNegativeYFlag
                                        , Control           playerUnit
                                        , Point             moveDistance) {
// An in-line call for the creating a loop task.
    await Task.Run(async () => {
        while (true) {
            await Task.Delay(10);
            loopCancellationToken.ThrowIfCancellationRequested();

// This is the actual loop that performs the moving by running both switchs until no more movement is needed.
            if (speedX != 0 || speedY != 0) {
                playerUnit.BeginInvoke(new Action(() => playerUnit.Location = new Point(playerUnit.Location.X + speedX 
                                                                                 , playerUnit.Location.Y + speedY)));

// This switch checks the X distance and stops X movement upon reaching the desired location by setting the movement speed to 0.
                switch (moveNegativeXFlag) {
                    case false when moveDistance.X > 16:
                        moveDistance.X -= Math.Abs(speedX);
                        break;

                    case false:
                        speedX = 0;
                        break;

                    default:
                        if (moveDistance.X > -16)
                            moveDistance.X -= Math.Abs(speedX);
                        else
                            speedX = 0;
                            break;
                }

// This switch checks the Y distance and stops Y movement upon reaching the desired location by setting the movement speed to 0.
                switch (moveNegativeYFlag) {
                    case false when moveDistance.Y > 16:
                        moveDistance.Y -= Math.Abs(speedY);
                        break;

                    case false:
                        speedY = 0;
                        break;

                    default:
                        if (moveDistance.Y > -16)
                            moveDistance.Y -= Math.Abs(speedY);
                        else
                            speedY = 0;
                            break;
                }
            } else {
                break;
            }
       }
   }
 , loopCancellationToken);
}


编辑后的结果:

现在,代码已得到改进,但仍然存在问题。除非您单击得非常快,否则该程序不再会降低速度并最大化CPU使用率。公羊的蠕变仍然缓慢。如果单击,它会异步工作,但无法取消前一个动作,并且它们只是重叠(例如,如果您在同一位置多次单击,它将移动得更快,如果您在相反的方向单击一次,它将一直停留在那里,直到您再次输入单击,以便一个可以克服另一个。)

最佳答案

感谢@ user1672994的帮助,我找到了为什么一切都坏了的原因。我需要对代码进行以下更改才能使其正常工作:


在主要方法中,我将loopRunning变量初始化为false,并且由于我仍然不知道的原因,它将在每次鼠标单击事件时将其重置为false。因此,要纠正这一点,我只是将其设为属性。
在循环开始时,我将playerUnit.Invoke更改为playerUnit.BeginInvoke
我为while循环添加了带有elsebreak语句。
我必须更改while循环条件,因为在引发异常时它不会取消,因此我现在仅将loopRunning变量用于条件。
最后,尽管我仍在完成此过程,但是如果调用取消,则需要使用新的CancellationTokenSource()

关于c# - 如何在C#中使用鼠标允许异步控制?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50375866/

相关文章:

c# - 比较2个队列,找到两个队列中都不存在的元素

c# - 解决对象的最佳方法是什么?

c# - 等待任务取消永远等待

multithreading - 如何在Websocket中使用超时,同时又不影响应用程序安全性?

c# - 从类型为 void 的方法返回任务

python - Celery中的task_reject_on_worker_lost和task_acks_late有什么不同

c# - 为什么 ConcurrentBag<T> 不实现 ICollection<T>?

c# - 方法重载还是更优雅的东西?

javascript - 尝试了解如何在js中使用Promise

node.js - 异步系列未按预期顺序工作