Task.Delay的准确性

时间:2015-07-31 09:19:00

标签: windows-runtime task-parallel-library win-universal-app

我正在使用C#/ Xaml开发Windows 10 Universal App,我正在使用等待 Task.Delay(delayInMilliseconds)来暂停给定时间的方法。我的场景有点实时,所以它对时间变化非常敏感,我需要确保当我暂停一个方法让我们说8毫秒时,它被暂停8个小时。我注意到** Task.Delay **暂停方法的实际时间跨度与传递的延迟参数不同,1到30毫秒,每次调用“偏差”的长度不同。所以,当我想睡8毫秒时,我的系统会睡眠9到39毫秒,这完全破坏了我的情景。 所以,我的问题是什么是替换** Task.Delay **并获得良好精度的最佳方法?目前我使用这种方法:

 public static void Delay1(int delay)
    {
        long mt = delay * TimeSpan.TicksPerMillisecond;
        Stopwatch s = Stopwatch.StarNew();
        while (true)
        {
            if (s.Elapsed.TotalMilliseconds > delay)
            {
                return;
            }
        }
    }

但它猜测它消耗了大量资源,即处理器核心的100%。如果用户的处理器核心数量很少,那将非常不足。

5 个答案:

答案 0 :(得分:5)

根据msdn,由于系统时钟分辨率,无法实现更高的准确度:

  

此方法取决于系统时钟。这意味着时间   延迟将近似等于系统时钟的分辨率   millisecondsDelay参数小于的分辨率   系统时钟,在Windows上约为15毫秒   系统

答案 1 :(得分:1)

您应该使用多媒体计时器。这些都很准确。 看看这里: https://social.msdn.microsoft.com/Forums/vstudio/en-US/2024e360-d45f-42a1-b818-da40f7d4264c/accurate-timer

答案 2 :(得分:1)

好像我找到了一个令人满意的解决方案:

new System.Threading.ManualResetEvent(false).WaitOne(delayInMilliseconds)而不是 await Task.Delay(delayInMilliseconds);

我使用以下代码来测试这两种方法:

async void Probe()
    {
        for (int i = 0; i < 1000; i++)
        {
            // METHOD 1
            await Task.Delay(3); 
           // METHOD 2
           new System.Threading.ManualResetEvent(false).WaitOne(3); 
         }
    }

上面的代码应该花费3秒钟才能执行。使用METHOD 2需要大约3300 ms,因此每次调用的误差为0.3 ms。我能接受。但是方法1执行大约15秒(!)来执行,这给我的场景带来了完全不可接受的错误。我只想知道使用方法2的CPU的用法是什么,希望它不使用轮询,文档说明了信号的使用,但不幸的是它并不自动意味着轮询不会在其他地方使用。

答案 3 :(得分:0)

努力工作后,我找到了一个让线程在特定时间内休眠的解决方案,我的Core 2 Duo CPU 3.00 GHz上的误差仅为3到4微秒。 这是:enter image description here

这是代码。(C#代码也在最后给出。)使用&#34; ThreadSleepHandler&#34;:

Public Class ThreadSleepHandler
Private Stopwatch As New Stopwatch
Private SpinStopwatch As New Stopwatch
Private Average As New TimeSpan
Private Spinner As Threading.SpinWait
Public Sub Sleep(Time As TimeSpan)
    Stopwatch.Restart()
    If Average.Ticks = 0 Then Average = GetSpinTime()
    While Stopwatch.Elapsed < Time
        If Stopwatch.Elapsed + Average < Time Then
            Average = TimeSpan.FromTicks((Average + GetSpinTime()).Ticks / 2)
        End If
    End While
End Sub
Public Function GetSpinTime() As TimeSpan
    SpinStopwatch.Restart()
    Spinner.SpinOnce()
    SpinStopwatch.Stop()
    Return SpinStopwatch.Elapsed
End Function
End Class

以下是示例代码:

Sub Main()
    Dim handler As New ThreadSleepHandler
    Dim stopwatch As New Stopwatch
    Do
        stopwatch.Restart()
        handler.Sleep(TimeSpan.FromSeconds(1))
        stopwatch.Stop()
        Console.WriteLine(stopwatch.Elapsed)
    Loop
End Sub

对于c#程序员来说这里是代码(我已经转换了这段代码,但我不确定):

static class Main
{
public static void Main()
{
    ThreadSleepHandler handler = new ThreadSleepHandler();
    Stopwatch stopwatch = new Stopwatch();
    do {
        stopwatch.Restart();
        handler.Sleep(TimeSpan.FromSeconds(1));
        stopwatch.Stop();
        Console.WriteLine(stopwatch.Elapsed);
    } while (true);
}
}
public class ThreadSleepHandler
{
private Stopwatch Stopwatch = new Stopwatch();
private Stopwatch SpinStopwatch = new Stopwatch();
private TimeSpan Average = new TimeSpan();
private Threading.SpinWait Spinner;
public void Sleep(TimeSpan Time)
{
    Stopwatch.Restart();
    if (Average.Ticks == 0)
        Average = GetSpinTime();
    while (Stopwatch.Elapsed < Time) {
        if (Stopwatch.Elapsed + Average < Time) {
            Average = TimeSpan.FromTicks((Average + GetSpinTime()).Ticks / 2);
        }
    }
}
public TimeSpan GetSpinTime()
{
    SpinStopwatch.Restart();
    Spinner.SpinOnce();
    SpinStopwatch.Stop();
    return SpinStopwatch.Elapsed;
}
}

注意: &#34; ThreadSleepHandler&#34;线程不安全。你不能使用单个&#34; ThreadSleepHandler&#34;多线程。

第一次睡眠时间不够准确。

答案 4 :(得分:0)

在我的测试中,我发现如果必须在代码中执行,则20ms间隔的DispatcherTimer将提供足够平滑的动画。如上所述,在XAML中执行此操作是另一种选择。