【问题标题】:Async Task.Delay overhead in UWP app, is there a better solution?UWP 应用程序中的 Async Task.Delay 开销,有更好的解决方案吗?
【发布时间】:2017-10-02 17:06:51
【问题描述】:

前段时间,我构建了一个在我的 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 线程出现问题,但我的应用程序是无头的,因此它并没有真正影响我的特定应用程序。但它仍然感觉有点像 hack,就像这里应该有更好的解决方案来获得相当准确的毫秒或更短的时间延迟。

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

谢谢。

【问题讨论】:

  • 和这里基本一样:stackoverflow.com/q/7137121/993547
  • @evk 看起来 System.Threading.Timer 可用,可能值得尝试。
  • there should be a better solution here to get a reasonably accurate millisecond-or-less time delay. - 仅当您认为 .NET 是实时环境时,事实并非如此。
  • 不幸的是,您对此无能为力。如果您在 Windows 上需要亚毫秒级精度,则不能让线程进入睡眠状态。通常你会使用Thread.SpinWait 而不是循环,不幸的是它在 UWP 上不可用

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


【解决方案1】:

就个人而言,我不认为在您的代码中添加延迟是解决问题的正确方法。如果您需要延迟以使某些 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
}`

将其作为一个类,您可以从任何 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 中可用的空条件运算符,我知道这很明显,但在另一个答案中遇到了类似的问题。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-29
    • 1970-01-01
    • 1970-01-01
    • 2016-03-27
    • 1970-01-01
    • 2020-02-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多