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

标签 c# raspberry-pi uwp async-await windows-10-iot-core

前段时间我构建了一个在我的 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
}

理论上,在循环的每次迭代中有 2 毫秒的延迟,这应该花费大约 3.2 秒。实际上,它最终花费了大约 51 秒。据我所知,调用此异步延迟方法的行为会增加大约 15 毫秒的开销来启动异步线程。如果我只是偶尔使用更长的延迟时间,那么这不会很明显。但是当我不得不这样做成百上千次时,它加起来很快。

经过大量挖掘,我找到了适合我的解决方案。我放弃了异步方法并使用 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
}

我假设小 while 循环可能会导致 UI 线程出现问题,但我的应用程序是 headless 的,因此它并没有真正影响我的特定应用程序。但它仍然感觉有点像 hack,就像这里应该有更好的解决方案来获得相当准确的毫秒或更短的时间延迟。

我承认我感觉我对async/await不是很了解,想知道这里有没有更合适的解决方案。因此,如果您在这里有一些专业知识并且可以解释更好的方法,或者可以解释为什么可以接受这种方法,我们将不胜感激。

谢谢。

最佳答案

就我个人而言,我不认为在您的代码中添加延迟是正确的方法。如果 persay 您需要应用延迟以使某些 UWP(最好是 Windows 10 IOT 核心)应用程序正常运行,那么可能可以通过更好的方式来避免延迟,因为延迟不仅是不准确的,而且是不可靠的了解方式工作已经完成,尤其是涉及到物联网项目时。事情随时都可能出错,手术时间可能会更长。在这种情况下,您的延迟中断并且您的物联网设置开始困惑。

那个存在说: 我已经编写了一个可以帮助您毫不拖延地控制步进电机的类(class),这是一件很快的事情,所以如果有任何问题请告诉我,但我已经对其进行了彻底的测试并且我似乎没有发现任何问题功能或性能方面。我的代码如下:

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
}`

将其作为一个类,您可以从任何 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);

现在您已完成设置,请按以下方式使用上面的内容:

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

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

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

有什么需要可以在评论区留言

关于c# - UWP 应用程序中的 Async Task.Delay 开销,是否有更好的解决方案?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43784236/

相关文章:

regex - 尝试用pihole正则表达式YouTube广告

python - 带有Raspberry pi 2b +的超声波传感器会产生编译错误

visual-studio - 运行时未部署 Visual Studio

c# - 使用 C# 在 SharePoint 中读取和查看文件

c# - 设置窗口在用户屏幕上的默认位置

c# - 我如何使用其内容知道文件是 txt 还是二进制文件?

linux - Uinput 和树莓派

c# - 用于撤消命令的 UWP TextBox 方法

c# - 在 C# 中测试 session 超时

c# - .Net Razor - 在页面之间共享变量