MessageBox 和 while 循环 C#

MessageBox and while loop C#

我正在修改现有的 C# 代码以驾驶活塞。每 30 毫秒,我都会通过一个事件直接反馈这个活塞的位置。该值存储在我用来获取活塞当前位置的全局变量中。

我要实现的目标:对于给定的距离输入 (A->C),我希望活塞全速行进 95% 的距离 (A->B),然后减慢速度对于剩余的 5% (B->C)。

我可以访问定义活塞速度和目的地的命令:pos(velocity, destination)。

但是,如果我编写该代码:

pos(fullSpeed,B);
pos(reducedSpeed, C);

活塞直接从全速变为减速

我尝试使用 while 循环将活塞的当前位置与目标位置进行比较,但是,在进入 while 循环后,存储活塞位置的变量不再更新。

但是,我注意到通过在中间插入一个 MessageBox,位置值会不断更新,我只需单击 "ok" 即可启动第二个命令。

pos(fullSpeed,B);
MessageBox.show("Wait");
pos(reducedSpeed, C);

我想知道为什么 "while" 循环会停止位置变量的更新,而 MessageBox 不会。我的意思是,只要我不单击 "ok" 按钮,该框就会阻止我做任何事情,这对我来说类似于 while 循环行为。除了 MessageBox 之外,还有其他方法可以代替 MessageBox 吗?

我对 C# 知之甚少,也没有任何支持。我试图查看文档,但没有找到答案(我可能错过了)。任何线索都非常受欢迎。

编辑:我没有该代码的文档,而且几乎没有评论。这是我收集的(真的希望它有帮助):

要移动活塞,调用一个函数:

MyEdc.Move.Pos(control, speed, destination, ref MyTan);

control 简单地定义了我们驾驶的内容(距离或负载,它是一个枚举),我不知道 MyTan 做了什么。我唯一知道的是 MyEdc.Move.Pos returns 错误代码。

如果我查看 "pos" 的定义,我将被重定向到 class

public DoPEmove Move;

其中包含:

public DoPE.ERR Pos(DoPE.CTRL MoveCtrl, double Speed, double Destination, ref short Tan);

DoPE.ERR 也是一种类型枚举。但是,我无法到达名为 "Pos" 的函数的定义。它可以在包含的 .dll 中吗?

以下是允许我访问活塞位置的代码(没有全局变量):

private int OnData(ref DoPE.OnData Data, object Parameter)
    {
      if (Data.DoPError == DoPE.ERR.NOERROR)
      {
        DoPE.Data Sample = Data.Data;
        Int32 Time = Environment.TickCount;
        if ((Time - LastTime) >= 300 /*ms*/)
        {
          LastTime = Time;
          string text;
          text = String.Format("{0}", Sample.Time.ToString("0.000"));
          guiTime.Text = text;
          text = String.Format("{0}", Sample.Sensor[(int)DoPE.SENSOR.SENSOR_S].ToString("0.000"));
          guiPosition.Text = text;
          text = String.Format("{0}", Sample.Sensor[(int)DoPE.SENSOR.SENSOR_F].ToString("0.000"));
          guiLoad.Text = text;
          text = String.Format("{0}", Sample.Sensor[(int)DoPE.SENSOR.SENSOR_E].ToString("0.000"));
          guiExtension.Text = text;
        }
      }
      return 0;
    }

使用

调用
MyEdc.Eh.OnDataHdlr += new DoPE.OnDataHdlr(OnData);

我意识到我对软件的运作方式知之甚少,这对您来说是多么令人沮丧。如果您认为这是一个失败的原因,没问题,我会尝试 Timothy Jannace 解决方案,如果它对我没有帮助,我会坚持使用 MessageBox 解决方案。我只是想知道为什么 MessageBox 允许我实现我的目标,而 while 循环却没有,以及如何在这里利用它来发挥我的优势。

我相信您想要做的是添加一个调用:

Application.DoEvents();

这将允许您的应用程序处理发布的消息(事件),从而允许更新该全局变量。

I just wanted to know why the MessageBox allowed me to sort of achieve my objectif, but the while loop did not, and how to use it in my advantage here.

之所以可行,是因为您让 WndProc 有机会处理已发送到应用程序的事件。这不是调用 MessageBox.Show(); 的预期功能,但它是一个结果。您可以通过调用 Application.DoEvents(); 来做同样的事情,而不会中断消息框。

如果您正在使用事件,您可能会遇到并发问题。特别是每 30 毫秒引发一次事件!

处理并发的一种非常简单的方法是使用锁对象来防止不同线程同时使用竞争资源:

class MyEventHandler
{
    private object _lockObject;

    MyEventHandler()
    {
        _lockObject = new object();
    }

    public int MyContestedResource { get; }

    public void HandleEvent( object sender, MyEvent event )
    {
        lock ( _lockObject )
        {
            // do stuff with event here
            MyContestedResource++;
        }
    }
}

请记住,这非常简单,并非在所有情况下都是完美的。如果您提供有关事件是如何引发的以及您正在用它们做什么的更多信息,人们将能够提供更多帮助。

编辑:

使用您为 Pos 方法发布的签名,我能够找到有关您正在使用的库的文档:https://www.academia.edu/24938060/Do_PE

之所以在定义时只看到方法签名是因为库已经编译成dll。实际上,无论如何查看代码可能都没有多大用处,因为看起来该库是原生(c 或 c++)代码的 C# 包装器。

无论如何,希望文档对您有所帮助。如果您查看第 20 页,就会看到一些关于运动的指示。这对新程序员来说是一个挑战,但你可以做到。我建议您避免使用事件处理程序来驱动您的逻辑,而是坚持使用 synchronous 版本的命令。使用同步命令,您的代码应该按照读取的方式运行。

I tried to use a while loop to compare the current position of the piston with the goal destination, however, upon entering the while loop, the variable storing the piston position does not update anymore.

当您处于 while 循环中时,您的应用无法再接收和处理反馈 事件

一个可能的解决方案是像这样使用 async/await

private const int fullSpeed = 1;
private const int reducedSpeed = 2;

private int currentPistonPositon = 0; // global var updated by event as you described

private async void button1_Click(object sender, EventArgs e)
{
    int B = 50;
    int C = 75;

    pos(fullSpeed, B);

    await Task.Run(() =>
        {   // pick one below?

            // assumes that "B" and "currentPistonPosition" can actually be EXACTLY the same value
            while (currentPistonPositon != B) 
            {
                System.Threading.Thread.Sleep(25);
            }

            // if this isn't the case, then  perhaps when it reaches a certain threshold distance?
            while (Math.Abs(currentPistonPositon - B) > 0.10)
            {
                System.Threading.Thread.Sleep(25);
            }
        });

    pos(reducedSpeed, C);
}

请注意 button1_Click 方法签名已用 async 标记。由于 await,代码将等待任务内的 while 循环完成,同时仍在处理 event 消息。只有这样,它才会继续进行第二个 pos() 调用。

Thank you for your answer ! It works like a charm ! (good catch on the EXACT value). I learnt a lot, and I am sure the async/await combo is going to be very usefull in the future ! – MaximeS

如果效果不错,那么您可能需要考虑重构代码并制作您自己的 "goto position" 方法,如下所示:

private void button1_Click(object sender, EventArgs e)
{
    int B = 50;
    int C = 75;

    GotoPosition(fullSpeed, B);
    GotoPosition(reducedSpeed, C);
}

private async void GotoPosition(int speed, int position)
{
    pos(speed, position);

    await Task.Run(() =>
    {
        while (Math.Abs(currentPistonPositon - position) > 0.10)
        {
            System.Threading.Thread.Sleep(25);
        }
    });
}

可读性会大大提高。

您甚至可以更花心地在 while 循环中引入 timeout 概念。现在您的代码可以执行如下操作:

private void button1_Click(object sender, EventArgs e)
{
    int B = 50;
    int C = 75;

    if (GotoPosition(fullSpeed, B, TimeSpan.FromMilliseconds(750)).Result)
    {
        if (GotoPosition(reducedSpeed, C, TimeSpan.FromMilliseconds(1500)).Result)
        {
            // ... we successfully went to B at fullSpeed, then to C at reducedSpeed ...
        }
        else
        {
            MessageBox.Show("Piston Timed Out");
        }
    }
    else
    {
        MessageBox.Show("Piston Timed Out");
    }

}

private async Task<bool> GotoPosition(int speed, int position, TimeSpan timeOut)
{
    pos(speed, position); // call the async API

    // wait for the position to be reached, or the timeout to occur
    bool success = true; // assume we have succeeded until proven otherwise
    DateTime dt = DateTime.Now.Add(timeOut); // set our timeout DateTime in the future
    await Task.Run(() =>
    {
        System.Threading.Thread.Sleep(50); // give the piston a chance to update maybe once before checking?
        while (Math.Abs(currentPistonPositon - position) > 0.10) // see if the piston has reached our target position
        {
            if (DateTime.Now > dt) // did we move past our timeout DateTime?
            {
                success = false;
                break;
            }
            System.Threading.Thread.Sleep(25); // very small sleep to reduce CPU usage
        }
    });
    return success;
}