【问题标题】:Task.Delay in .net fires 125ms early.net 中的 Task.Delay 提前 125 毫秒触发
【发布时间】:2017-11-14 11:38:45
【问题描述】:

所以我有一个非常奇怪的行为,c# 任务延迟有点让我发疯。

上下文: 我正在使用 C# .net 通过 R4852 与我们的一台设备进行通信。设备完成每个命令大约需要 200 毫秒,因此我在通信类中引入了 250 毫秒的延迟。

错误/不良行为:我的通信类中的延迟有时会等待 250 毫秒,有时只等待 125 毫秒。这是可重现的,并且当我增加延迟时会发生相同的行为。例如。如果我将延迟设置为每秒 1000 毫秒,请求将只等待 875 毫秒,所以再次缺少 125 毫秒。 此行为仅在没有附加调试器的情况下发生,并且仅在某些机器上发生。我们生产部门将使用该软件的机器有这个问题,我现在正在使用的机器没有这个问题。两者都运行 Windows 10。

怎么时不时少了125ms? 我已经了解到 Task.Delay 方法使用的是精度为 15 毫秒的计时器。这并不能解释缺少的 125 毫秒,因为它最多应该延迟几毫秒而不是过早 125 毫秒。

以下方法是我用来将命令排队到我的设备的方法。有一个信号量负责,因此一次只能执行一个命令(_requestSemapohre),因此只能处理一个请求。

public async Task<bool> Request(WriteRequest request)
{
    await _requestSemaphore.WaitAsync();    // block incoming calls
    await Task.Delay(Delay);                // delay
    Write(_connectionIdDictionary[request.Connection], request.Request);           // write
    if (request is WriteReadRequest)
    {
        _currentRequest = request as WriteReadRequest;
        var readSuccess = await _readSemaphore.WaitAsync(Timeout); // wait until read of line has finished
        _currentRequest = null;                 // set _currentRequest to null
        _requestSemaphore.Release();            // release next incoming call
        if (!readSuccess)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    else
    {
        if (request is WriteWithDelayRequest)
        {
            await Task.Delay((request as WriteWithDelayRequest).Delay);
        }
        _requestSemaphore.Release();            // release next incoming call
        return true;
    }
}

以下代码是将请求发送到上述方法的方法的一部分。我删除了一些行以保持简短。基本的东西(请求和等待)仍然存在

// this command is the first command and will always have a proper delay of 1000ms
var request = new Communication.Requests.WriteRequest(item.Connection, item.Command);
await _translator.Request(request);

// this request is the second request that is missing 125ms
var queryRequest = new Communication.Requests.WriteReadRequest(item.Connection, item.Query);    // query that is being sent to check if the value has been sent properly
if (await _translator.Request(queryRequest))    // send the query to the device and wait for response
{
    if (item.IsQueryValid(queryRequest.Response))   // check result
    {
        item.Success = true;
    }
}

我发送给此方法的第一个请求是WriteRequest,第二个请求是WriteReadRequest

我在使用名为 Device Monitoring Studio 的软件监视串行通信时查看串行端口通信时发现了这种行为。

这是实际串行通信的屏幕截图。在这种情况下,我使用了 1000 毫秒的延迟。您可以看到sens0002 命令在执行之前正好有 1 秒的延迟。下一个命令/查询sens?只有 875ms 的延迟。此屏幕截图是在未附加调试器时拍摄的。

这是另一个屏幕截图。延迟再次设置为 1000 毫秒,但这次附加了调试器。如您所见,第一个和第二个命令现在都有大约 1000 毫秒的延迟。

在以下两个屏幕截图中,您可以看到相同的行为,但延迟为 250 毫秒(已降至 125 毫秒)。第一个没有附加调试器的屏幕截图,第二个附加了调试器的屏幕截图。在第二个屏幕截图中,您还可以看到 35 毫秒的漂移很安静,但仍远不及之前丢失的 125 毫秒。

那我到底在看什么?快速而肮脏的解决方案是将延迟增加到 1000 毫秒,这样就不再是问题了,但我宁愿理解为什么会出现这个问题以及如何正确解决它。

干杯!

【问题讨论】:

  • 愚蠢的问题 - 你为什么不 Thread.Sleep 代替?
  • @zaitsman 这样会阻塞主线程,不推荐
  • 另外,不清楚是什么在调用您的 Request() 方法,因此它可能与它有关
  • 线程交换会导致这样的延迟——如果线程没有运行,计时器将不会触发。还解释了为什么Thread.Sleep 会导致更严重的偏差
  • @SouvikGhosh 不,如果整个事情都被包装成ThreadTask.Run,它就不会了

标签: c# .net wpf asynchronous task


【解决方案1】:

据我所知,您的时间打印为上一个的增量。进入。

在 125/875 毫秒的情况下,您有 8 个中间条目,每个大约 15 毫秒(总和大约 120 毫秒)

在 250/1000 毫秒的情况下,您有 8 个中间条目,每个大约 5 毫秒(总和大约 40 毫秒),这些数字实际上更像 215/960 毫秒。

所以,如果你加上这些中间延迟,据我所知,由此产生的完整延迟大致相同。


为只想在问题标题上是/否的每个人回答问题:The First Rule of Programming: It's Always Your Fault

可以假设,Task.Delay 至少涵盖了指定的时间量(由于时钟分辨率可能更多)。因此,如果它似乎涵盖了更小的时间跨度,那么用于测试实际延迟的方法在某种程度上是错误的。

【讨论】:

  • @KollmannMichael 回应“15ms 是字符传输”(或您评论的类似内容):Write(_connectionIdDictionary[request.Connection], request.Request) 是否阻止执行直到所有字符都被写入,或者它只是填充缓冲区并返回?
  • 我认为您在这里有所作为。当我回到工作岗位时有机会查看我的代码时,我会回复你
  • 时序受操作系统时钟中断率影响。 15.625 毫秒是正常速率,5 毫秒不是。但是当程序与音乐播放器或网络浏览器同时运行时,往往会发生这种情况。允许更改速率的底层 winapi 函数是 timeBeginPeriod。每个时钟滴答只能发送一个字符是不健康的,应该修复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-15
  • 1970-01-01
  • 2012-02-23
  • 2020-04-06
  • 1970-01-01
相关资源
最近更新 更多