UWP应用程序中的Async Task.Delay开销是否有更好的解决方案?

Ben*_*ndt 7 c# async-await raspberry-pi windows-10-iot-core uwp

前段时间我在Raspberry Pi 3上构建了一个应用程序.其中,它使用步进电机驱动板控制步进电机.为了让电机向"步进"移动,我必须将输出引脚设置为高电平和低电平.电压的变化会导致电机进入下一个位置.我需要在电压变化之间有一个小的时间延迟才能正常工作.

我最初的研究表明,在UWP应用程序中获得时间延迟的最佳实践方法是使用异步Task.Delay()方法.我无法访问UWP中的Thread.Sleep方法,所以我试了一下.此外,由于该方法采用整数作为参数,因此1毫秒是我可以使用的最短延迟.

这是我第一次尝试的例子,制作了1600个连续的"步骤":

for (int i = 0; i < 1600; i++)
{
    // (stepper driver board makes one step for every low-to-high transition)
    StepperStepPin.Write(GpioPinValue.Low);
    await Task.Delay(1); // wait 1 ms
    StepperStepPin.Write(GpioPinValue.High);
    await Task.Delay(1); // wait 1 ms
}
Run Code Online (Sandbox Code Playgroud)

理论上,在循环的每次迭代中延迟2ms,这应该花费大约3.2秒.实际上,它最终需要大约51秒.据我所知,调用此异步延迟方法的行为会增加大约15 ms的开销来启动异步线程.如果我只是偶尔使用更长的延迟,这将是不明显的.但是,当我不得不做成百次或数千次时,它会迅速增加.

经过多次挖掘,我发现了一个适合我的解决方案.我放弃了异步方法并使用System.Diagnostics.Stopwatch类进行同步方法,它也让我有亚毫秒延迟:

private readonly Stopwatch _sw = new System.Diagnostics.Stopwatch();

private void ShortDelay(double milliseconds) {
    _sw.Start();
    while ((_sw.Elapsed).TotalMilliseconds < milliseconds) { }
    _sw.Reset();
}

//////////////////////////////////////////

for (int i = 0; i < 1600; i++)
{
    // (stepper driver board makes one step for every low-to-high transition)
    StepperStepPin.Write(GpioPinValue.Low);
    ShortDelay(0.5); // wait 0.5 ms
    StepperStepPin.Write(GpioPinValue.High);
    ShortDelay(0.5); // wait 0.5 ms
}
Run Code Online (Sandbox Code Playgroud)

我会假设while while循环可能会导致UI线程出现问题,但我的应用程序是无头的,所以它并没有真正影响我的特定应用程序.但它仍然感觉像是一个黑客,就像这里应该有一个更好的解决方案,以获得一个相当精确的毫秒或更短的时间延迟.

我承认我觉得我不完全理解异步/等待,我想知道这里是否有更合适的解决方案.因此,如果您在这里有一些专业知识并且可以解释更好的方法,或者可以解释为什么这种方法是可接受的,那么任何反馈都将受到赞

谢谢.

iam*_*rot 0

就我个人而言,我认为在代码中添加延迟并不是解决问题的正确方法。如果你需要应用延迟来让某些 UWP(最好是 Windows 10 IOT 核心)应用程序正常运行,那么可能可以采取更好的方法来避免延迟,因为延迟不仅是不准确且不可靠的了解方式这项工作已经完成,尤其是在物联网项目方面。事情随时可能出错,操作时间可能会更长。在这种情况下,你的延迟就会中断,你的物联网设置就会开始变得混乱。

话虽这么说: 我已经编写了一个课程,可以帮助您无延迟地控制步进电机,这是一件很快的事情,因此如果有任何问题请告诉我,但我已经对其进行了彻底的测试,而且我似乎没有查找功能或性能方面的任何问题。我的代码如下:

public class Uln2003Driver : IDisposable
{
    private readonly GpioPin[] _gpioPins = new GpioPin[4];

    private readonly GpioPinValue[][] _waveDriveSequence =
    {
        new[] {GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low},
        new[] {GpioPinValue.Low, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low},
        new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High, GpioPinValue.Low},
        new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High}
    };

    private readonly GpioPinValue[][] _fullStepSequence =
    {
        new[] {GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High},
        new[] {GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low},
        new[] {GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low},
        new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High }

    };

    private readonly GpioPinValue[][] _haveStepSequence =
    {
        new[] {GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High},
        new[] {GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low},
        new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low},
        new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High, GpioPinValue.High }
    };

    public Uln2003Driver(int blueWireToGpio, int pinkWireToGpio, int yellowWireToGpio, int orangeWireToGpio)
    {
        var gpio = GpioController.GetDefault();

        _gpioPins[0] = gpio.OpenPin(blueWireToGpio);
        _gpioPins[1] = gpio.OpenPin(pinkWireToGpio);
        _gpioPins[2] = gpio.OpenPin(yellowWireToGpio);
        _gpioPins[3] = gpio.OpenPin(orangeWireToGpio);

        foreach (var gpioPin in _gpioPins)
        {
            gpioPin.Write(GpioPinValue.Low);
            gpioPin.SetDriveMode(GpioPinDriveMode.Output);
        }
    }

    public async Task TurnAsync(int degree, TurnDirection direction,
        DrivingMethod drivingMethod = DrivingMethod.FullStep)
    {
        var steps = 0;
        GpioPinValue[][] methodSequence;
        switch (drivingMethod)
        {
            case DrivingMethod.WaveDrive:
                methodSequence = _waveDriveSequence;
                steps = (int) Math.Ceiling(degree/0.1767478397486253);
                break;
            case DrivingMethod.FullStep:
                methodSequence = _fullStepSequence;
                steps = (int) Math.Ceiling(degree/0.1767478397486253);
                break;
            case DrivingMethod.HalfStep:
                methodSequence = _haveStepSequence;
                steps = (int) Math.Ceiling(degree/0.0883739198743126);
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(drivingMethod), drivingMethod, null);
        }
        var counter = 0;
        while (counter < steps)
        {
            for (var j = 0; j < methodSequence[0].Length; j++)
            {
                for (var i = 0; i < 4; i++)
                {
                    _gpioPins[i].Write(methodSequence[direction == TurnDirection.Left ? i : 3 - i][j]);
                }
                await Task.Delay(5);
                counter ++;
                if (counter == steps)
                    break;
            }
        }

        Stop();
    }

    public void Stop()
    {
        foreach (var gpioPin in _gpioPins)
        {
            gpioPin.Write(GpioPinValue.Low);
        }
    }

    public void Dispose()
    {
        foreach (var gpioPin in _gpioPins)
        {
            gpioPin.Write(GpioPinValue.Low);
            gpioPin.Dispose();
        }
    }
}

public enum DrivingMethod
{
    WaveDrive,
    FullStep,
    HalfStep
}

public enum TurnDirection
{
    Left,
    Right
}`
Run Code Online (Sandbox Code Playgroud)

将其作为一个类,您可以从任何 CodeBehind 或 ViewModel 与它交互,如下所示:

 private readonly Uln2003Driver _uln2003Driver;  //The Declaration on top of the Class to make it global.


//In the constructor of the Page CodeBehind or ViewModel. The arguments are the GPIO pins to which your stepper is connected.
     _uln2003Driver = new Uln2003Driver(26, 13, 6, 5);
Run Code Online (Sandbox Code Playgroud)

现在您已完成设置,请使用上面的内容,如下所示:

 Task.Run(async () =>
             {
                 await _uln2003Driver.TurnAsync(180, TurnDirection.Left, DrivingMethod.FullStep);
                 await _uln2003Driver.TurnAsync(180, TurnDirection.Right, DrivingMethod.WaveDrive);
             });            
Run Code Online (Sandbox Code Playgroud)

上面的代码只是顺时针和逆时针方向旋转,但可以随意调整它,

注意:请记住_uln2003Driver?.Dispose();在页面卸载或作业完成后致电以释放资源。另外,?中还有一个空条件运算符c#6.0,我知道这是显而易见的,但在另一个答案中遇到了类似的问题。

如果您有任何需要,请随时使用评论部分