繁体   English   中英

等待Task.Delay(foo); 需要几秒钟而不是毫秒

[英]await Task.Delay(foo); takes seconds instead of ms

与类似IO的操作结合使用时,在Task.Delay使用可变延迟Task.Delay随机花费几秒钟而不是毫秒。

复制代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication {
    class Program {
        static void Main(string[] args) {

            Task[] wait = {
                              new delayTest().looper(5250, 20), 
                              new delayTest().looper(3500, 30),
                              new delayTest().looper(2625, 40), 
                              new delayTest().looper(2100, 50)
                          };
            Task.WaitAll(wait);

            Console.WriteLine("All Done");
            Console.ReadLine();
        }
    }
    class delayTest {
        private Stopwatch sw = new Stopwatch();

        public delayTest() {
            sw.Start();
        }

        public async Task looper(int count, int delay) {
            var start = sw.Elapsed;
            Console.WriteLine("Start ({0}, {1})", count, delay);
            for (int i = 0; i < count; i++) {
                var before = sw.Elapsed;
                var totalDelay = TimeSpan.FromMilliseconds(i * delay) + start;
                double wait = (totalDelay - sw.Elapsed).TotalMilliseconds;
                if (wait > 0) {
                    await Task.Delay((int)wait);
                    SpinWait.SpinUntil(() => false, 1);
                }
                var finalDelay = (sw.Elapsed - before).TotalMilliseconds;
                if (finalDelay > 30 + delay) {
                    Console.WriteLine("Slow ({0}, {1}): {4} Expected {2:0.0}ms got {3:0.0}ms", count, delay, wait, finalDelay, i);
                }
            }
            Console.WriteLine("Done ({0}, {1})", count, delay);
        }
    }
}

还报告了关于连接


为了完整起见,请保留旧的问题。

我正在运行一个任务,该任务从网络流中读取,然后延迟20ms,然后再次读取(执行500次读取,这大约需要10秒钟)。 当我仅阅读1个任务时,此方法效果很好,但是当我运行多个任务时,有些事情会延迟很长(60秒),这会发生奇怪的事情。 我的ms延迟任务突然挂了一半。

我正在运行以下代码 (简体):

var sw = Stopwatch();
sw.Start()
await Task.Delay(20); // actually delay is 10, 20, 30 or 40;
if (sw.Elapsed.TotalSeconds > 1) {
    Console.WriteLine("Sleep: {0:0.00}s", sw.Elapsed.TotalSeconds);
}

打印:

睡眠:11.87秒

(实际上,它使20ms的延迟达到了99%的时间,这些被忽略了)。

此延迟几乎比预期的时间长600倍。 同一延迟同时发生在3个不同的线程上,它们也同时在同一时间再次继续。

简短任务完成后,60秒的睡眠任务会在大约40秒后正常唤醒。

一半时间甚至没有发生此问题。 另一半的延迟时间为11.5-12秒。 我会怀疑调度或线程池问题,但是所有线程都应该是空闲的。

当我在卡住阶段暂停程序时,主线程stacktrace位于Task.WaitAll ,在await Task.Delay(20)上调度了3个任务,在await Task.Delay(60000)上调度了一个任务。 另外还有4个任务正在等待前四个任务,报告类似“任务24”的事件正在此对象上等待:“任务5313”(线程0拥有)。 所有4个任务都说等待任务归线程0所有。还有4个ContinueWith任务,我认为我可以忽略。

计划任务

还有其他事情在进行,例如第二个控制台应用程序写入网络流,但是一个控制台应用程序不应影响另一个。

我对此一无所知。 到底是怎么回事?

更新:

根据评论和问题:

当我运行4次程序时,它将挂起2-3次,持续10-15秒,然后将其正常运行1-2次(并且不会显示“睡眠:{0:0.00} s”。)

Thread.Count确实增加了,但是无论挂起如何,这种情况都会发生。 我只是在没有挂起的情况下运行,然后Thread.Count从24开始,在一秒钟后达到40,大约22秒,短任务正常完成,然后Thread.Count在接下来的40里缓慢下降到22。秒。

在下面的链接中找到更多代码,完整代码。 起始客户:

List<Task> tasks = new List<Task>();

private void makeClient(int delay, int startDelay) {
    Task task = new ClientConnection(this, delay, startDelay).connectAsync();
    task.ContinueWith(_ => {
        lock (tasks) { tasks.Remove(task); }
    });
    lock (tasks) { tasks.Add(task); }
}

private void start() {
    DateTime start = DateTime.Now;
    Console.WriteLine("Starting clients...");

    int[] iList = new[]  { 
        0,1,1,2,
        10, 20, 30, 40};
    foreach (int delay in iList) {
        makeClient(delay, 0); ;
    }
    makeClient(15, 40);
    Console.WriteLine("Done making");

    tasks.Add(displayThreads());

    waitForTasks(tasks);
    Console.WriteLine("All done.");
}

private static void waitForTasks(List<Task> tasks) {
    Task[] waitFor;
    lock (tasks) {
        waitFor = tasks.ToArray();
    }
    Task.WaitAll(waitFor);
}

此外,我试图更换Delay(20)await Task.Run(() => Thread.Sleep(20)) Thread.Count现在从29变为43和退缩到24,但是在多个符它从未挂起。

使用或不使用ThreadPool.SetMinThreads(500, 500) TaskExt.Delay ThreadPool.SetMinThreads(500, 500) ,使用TaskExt.Delay的TaskExt.Delay都不会挂起。 (也就是说,即使切换1行代码有时也会停止挂起,只是在我重新启动项目4次后随机继续,但是我已经连续尝试了6次而现在没有任何问题)。

到目前为止,我已经尝试了有无ThreadPool.SetMinThreads上述所有操作, ThreadPool.SetMinThreads未有任何不同。

Update2: CODE!

没有看到更多代码,很难做出进一步的猜测,但是我想总结一下这些注释,这可能会在将来对其他人有所帮助:

  • 我们发现这里ThreadPool口吃不是问题,因为ThreadPool.SetMinThreads(500, 500)并没有帮助。

  • 任务工作流中的任何地方都存在SynchronizationContext吗? Debug.Assert(SyncrhonizationContext.Current == null)各处进行检查。 在每次await使用ConfigureAwait(false)

  • 是否有任何.Wait.WaitOne.WaitAllWaitAny.Result在你的代码的任何地方使用? 任何lock () { ... }构造? Monitor.Enter/Exit或任何其他阻止同步原语?

  • 关于此: 我已经用Task.Delay(20)替换了Task.Delay(20) Task.Yield(); Thread.Sleep(20) Task.Yield(); Thread.Sleep(20)作为一种解决方法,可以正常工作。 但是,是的,我继续尝试弄清楚这里发生了什么,因为Task.Delay(20)可以拍摄如此远的距离的想法使其完全无法使用。

    确实,这听起来令人担忧。 Task.Delay几乎没有错误,但一切Task.Delay可能。 为了进行试验,请尝试使用await Task.Run(() => Thread.Sleep(20))替换await Task.Delay(20) await Task.Run(() => Thread.Sleep(20)) ,同时将ThreadPool.SetMinThreads(500, 500) await Task.Run(() => Thread.Sleep(20)) ThreadPool.SetMinThreads(500, 500)保留在原位。

    我还有一个使用非托管CreateTimerQueueTimer API的Delay的实验实现(不同于Task.Delay使用System.Threading.Timer ,后者又使用托管TimerQueue )。 可以在这里找到要点 请随意将其作为TaskExt.Delay而不是标准Task.Delay进行尝试。 计时器回调已发布到ThreadPool ,因此该实验仍应使用ThreadPool.SetMinThreads(500, 500) 我怀疑这可能会有所不同,但是我很想知道。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM