【问题标题】:await Task.Delay(foo); takes seconds instead of ms等待 Task.Delay(foo);需要几秒钟而不是毫秒
【发布时间】:2015-04-13 23:13:37
【问题描述】:

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-delay 任务突然挂了一半。

我正在运行以下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% 的情况下会延迟 20 毫秒,这些都被忽略了)。

此延迟几乎是预期的 600 倍。相同的延迟同时发生在 3 个单独的线程上,并且它们都在同一时间再次继续。

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

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

当我在卡住阶段暂停我的程序时,主线程堆栈跟踪位于Task.WaitAll,3 个任务计划在 await Task.Delay(20),一个任务计划在 await Task.Delay(60000)。还有 4 个任务等待前 4 个任务,报告诸如“任务 24”正在等待此对象:“任务 5313”(由线程 0 拥有)之类的内容。所有 4 个任务都说等待任务由线程 0 拥有。还有 4 个 ContinueWith 任务我认为可以忽略。

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

我对此一无所知。发生了什么事?

更新:

基于cmets和问题:

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

Thread.Count 确实上升了,但无论挂起如何,都会发生这种情况。我刚刚进行了一次没有挂起的跑步,Thread.Count 从 24 开始,1 秒后上升到 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(() =&gt; Thread.Sleep(20)) Thread.Count 现在从 29 变为 43 并回落到 24,但在多个符文中它永远不会挂起。

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

到目前为止,无论有没有ThreadPool.SetMinThreads,我都尝试了上述所有方法,但没有任何区别。

更新2:CODE!

【问题讨论】:

  • 请展示一个简短但完整的程序来说明问题 - 这样帮助你会容易得多。 (我们目前对您的环境一无所知...)
  • 现在这将是两个独立的控制台程序,带有很多额外的代码。最大的问题是bug只弹出一半的时间,做一些小的改动随机消除它。但我会看看我能不能做点什么。
  • 是否设置了 SynchronizationContext? Task.WaitAll 是您等待任务的唯一点吗?
  • @Noseratio 好预感。如果没有阻塞,线程数不应增加 21。有阻塞,只是这里不可见。
  • 试试这个:var t = new List&lt;Task&gt;(1000); for (int i = 0; i &lt; 1000; i++) t.Add(Task.Run(async () =&gt; { await Task.Delay(500); })); Task.WhenAll(t).ContinueWith(t1 =&gt; { Console.WriteLine("Done!"); 应该立即完成,因为它应该。但是延迟更小。表明这实际上不是延迟或线程数的问题。您在某处阻塞,或者您有不同的同步上下文限制。除非您发布更多代码,否则社区无法帮助您!

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


【解决方案1】:

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

  • 我们发现 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.Yield(); Thread.Sleep(20) 替换了Task.Delay(20) 作为一种解决方法,这是可行的。但是,是的,我继续尝试弄清楚这里发生了什么,因为 Task.Delay(20) 可以射出这么远的想法使它完全无法使用。

    这听起来确实令人担忧。 Task.Delay 中不太可能存在错误,但一切皆有可能。为了进行实验,请尝试将 await Task.Delay(20) 替换为 await Task.Run(() =&gt; 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) 仍应用于此实验。我怀疑这会有所不同,但我很想知道。

【讨论】:

  • 谢谢,这是我开始的好地方。我已经更新了我的问题。
  • @Dorus,不确定这是否会改变任何事情,但 1) 我不会像你那样锁定 taskstask.ContinueWith(_ =&gt; { lock (tasks) { tasks.Remove(task); }})。创建一个单独的唯一Object,仅用于lock。 2)我会在那里使用TaskContinuationOptions.ExecuteSynchronouslyContinueWith(或者更好的是async/await)。 3)SyncrhonizationContext呢?
  • 是的,我的代码很丑,终于有时间发布一些最小的东西。 1)同意,没有区别。 2)试过这个,也没有区别。 3) 你是什么意思?
  • 由于我不期待更多答案,而且这个答案非常有帮助,我会尽快接受。最后一个问题,如果可以的话。这看起来很可疑,我应该在其他地方跟进吗?某个论坛?也许是错误报告?如果是这样,您建议在哪里这样做?
  • Done。尽我所能减少它,希望它足够了。
猜你喜欢
  • 2014-04-12
  • 2020-04-06
  • 1970-01-01
  • 1970-01-01
  • 2021-08-24
  • 1970-01-01
  • 2019-10-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多