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

时间:2015-02-25 11:53:59

标签: c# .net task-parallel-library async-await

使用Task.Delay中的可变延迟,当与类似IO的操作结合使用时,随机需要几秒而不是几毫秒。

重现的代码:

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);
        }
    }
}

还在connect上报告此内容。


为了完整性,请留下旧问题。

我正在运行一个从网络流中读取的任务,然后延迟20毫秒,然后再次读取(执行500次读取,这应该需要大约10秒)。当我只使用1个任务读取时,这很有效,但是当我有多个任务运行时会发生奇怪的事情,有些任务会延迟很长(60秒)。我的ms延迟任务突然中途停止了。

我正在运行以下code(简化):

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秒

(实际上它在99%的时间内给出了20ms的延迟,忽略了这些延迟)。

这种延迟几乎是预期的600倍。同时在3个独立的线程上发生相同的延迟,并且它们也会同时继续。

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

有一半时间甚至没有发生这个问题。另一半,它有一致的延迟11.5-12秒。我怀疑调度或线程池问题,但所有线程都应该是免费的。

当我在卡住阶段暂停我的程序时,主线程堆栈跟踪站在Task.WaitAll上,3个任务在await Task.Delay(20)上预定,一个任务在await Task.Delay(60000)上预定。还有4个任务等待前4个任务,报告诸如任务24和#34;正在等待这个对象:&#34;任务5313&#34; (由线程0拥有)&#39;。所有4个任务都说等待任务由线程0拥有。还有4个ContinueWith任务我认为我可以忽略。

Scheduled tasks

还有一些其他的事情发生,比如第二个控制台应用程序写入网络流,但一个控制台应用程序不应该影响另一个。

我对此完全无能为力。发生了什么事?

更新

基于评论和问题:

当我运行我的程序4次时,2-3次将挂起10-15秒,1-2次将正常运行(并且不打印&#34;睡眠:{0:0.00} s&# 34。)

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

更多代码,完整代码可在以下链接中找到。启动客户:

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 by noserati它不会挂起。 (也就是说,即使切换1行代码,有时也会停止挂起,只是在我重新启动项目4次后随机继续,但我已经连续尝试了6次而没有任何问题)。

到目前为止,无论有没有ThreadPool.SetMinThreads,我都尝试过以上所有内容,从未有过任何改变。

Update2:CODE!

1 个答案:

答案 0 :(得分:2)

如果没有看到更多代码,很难做出进一步的猜测,但我想总结一下这些评论,它可能会对未来的其他人有所帮助:

  • 我们已经发现the ThreadPool stuttering不是问题,因为ThreadPool.SetMinThreads(500, 500)没有帮助。

  • 任务工作流程中的任何位置都有SynchronizationContext吗?将Debug.Assert(SyncrhonizationContext.Current == null)放在任何地方以检查。每ConfigureAwait(false)使用await

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

  • 对此:我已经用Task.Delay(20)取代Task.Yield(); Thread.Sleep(20)作为解决方法,这有效。但是,是的,我继续试图弄清楚这里发生了什么,因为Task.Delay(20)可以拍摄这么远的想法使得它完全无法使用。

    这确实令人担忧。 Task.Delay中的错误不太可能,但一切皆有可能。为了进行试验,请尝试将await Task.Delay(20)替换为await Task.Run(() => Thread.Sleep(20))ThreadPool.SetMinThreads(500, 500)仍然就位。

    我还有Delay的实验性实施,它使用了非管理CreateTimerQueueTimer API(与Task.Delay不同,后者使用System.Threading.Timer,后者又使用托管TimerQueue )。它可用here as a gist。您可以将其作为TaskExt.Delay而不是标准Task.Delay进行尝试。计时器回调过帐到ThreadPool,因此ThreadPool.SetMinThreads(500, 500)仍应用于此实验。我怀疑它可能有什么不同,但我有兴趣知道。