等待 Task.Delay(foo);需要几秒而不是毫秒
await Task.Delay(foo); takes seconds instead of ms
在 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.
上报告了这一点
为了完整起见,下面留下老问题。
我正在 运行ning 一个从网络流读取的任务,然后延迟 20 毫秒,然后再次读取(进行 500 次读取,这应该需要大约 10 秒)。当我只阅读 1 个任务时,这很有效,但是当我有多个任务 运行ning 时会发生奇怪的事情,有些任务有很长(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);
}
这会打印:
Sleep: 11.87s
(实际上它在 99% 的时间里给出了 20ms 的延迟,那些被忽略了)。
这个延迟几乎是预期的 600 倍。同样的延迟同时发生在 3 个独立的线程上,并且它们都在同一时间再次继续。
60 秒休眠任务在短任务完成后约 40 秒正常唤醒。
有一半时间这个问题甚至不会发生。另一半,它有 11.5-12 秒的一致延迟。我会怀疑调度或线程池问题,但所有线程都应该是空闲的。
当我在卡住阶段暂停我的程序时,主线程堆栈跟踪站在 Task.WaitAll
,3 个任务被安排在 await Task.Delay(20)
,一个任务被安排在 await Task.Delay(60000)
。还有 4 个任务正在等待前 4 个任务,报告类似“"Task 24" 正在等待此对象:"Task 5313"(由线程 0 拥有)”之类的内容。 4个任务都说等待任务归线程0所有。还有4个ContinueWith任务我觉得可以忽略。
还有一些其他事情正在发生,例如写入网络流的第二个控制台应用程序,但一个控制台应用程序不应影响另一个。
我对这个一窍不通。这是怎么回事?
更新:
基于评论和问题:
当我 运行 我的程序 4 次时,2-3 次它会挂起 10-15 秒,1-2 次它会正常运行(并且不会打印 "Sleep: {0:0.00}s"。)
Thread.Count
确实上升了,但是无论挂起都会发生这种情况。我刚刚有一个 运行 它没有挂起,Thread.Count
从 24 开始,1 秒后上升到 40,大约 22 秒短任务正常完成,然后 Thread.Count
wend在接下来的 40 秒内慢慢下降到 22。
更多代码,完整代码可在下面的 link 中找到。起始客户:
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
,我都尝试了上述所有方法,没有任何区别。
更新 2:CODE!
没有看到更多的代码,很难做出进一步的猜测,但我想总结一下评论,这可能对以后的其他人有所帮助:
我们发现 the ThreadPool
stuttering 不是问题,因为 ThreadPool.SetMinThreads(500, 500)
没有帮助。
您的任务流程中是否有任何 SynchronizationContext
?将 Debug.Assert(SyncrhonizationContext.Current == null)
放在各处进行检查。每个 await
.
使用 ConfigureAwait(false)
您的代码中是否使用了 .Wait
、.WaitOne
、.WaitAll
、WaitAny
、.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 (unlike Task.Delay
, which uses System.Threading.Timer
, which in turn uses managed TimerQueue
). It's available here as a gist。随意尝试使用 TaskExt.Delay
而不是标准的 Task.Delay
。计时器回调已发布到 ThreadPool
,因此本实验仍应使用 ThreadPool.SetMinThreads(500, 500)
。我怀疑这会不会有什么不同,但我很想知道。
在 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.
上报告了这一点为了完整起见,下面留下老问题。
我正在 运行ning 一个从网络流读取的任务,然后延迟 20 毫秒,然后再次读取(进行 500 次读取,这应该需要大约 10 秒)。当我只阅读 1 个任务时,这很有效,但是当我有多个任务 运行ning 时会发生奇怪的事情,有些任务有很长(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);
}
这会打印:
Sleep: 11.87s
(实际上它在 99% 的时间里给出了 20ms 的延迟,那些被忽略了)。
这个延迟几乎是预期的 600 倍。同样的延迟同时发生在 3 个独立的线程上,并且它们都在同一时间再次继续。
60 秒休眠任务在短任务完成后约 40 秒正常唤醒。
有一半时间这个问题甚至不会发生。另一半,它有 11.5-12 秒的一致延迟。我会怀疑调度或线程池问题,但所有线程都应该是空闲的。
当我在卡住阶段暂停我的程序时,主线程堆栈跟踪站在 Task.WaitAll
,3 个任务被安排在 await Task.Delay(20)
,一个任务被安排在 await Task.Delay(60000)
。还有 4 个任务正在等待前 4 个任务,报告类似“"Task 24" 正在等待此对象:"Task 5313"(由线程 0 拥有)”之类的内容。 4个任务都说等待任务归线程0所有。还有4个ContinueWith任务我觉得可以忽略。
还有一些其他事情正在发生,例如写入网络流的第二个控制台应用程序,但一个控制台应用程序不应影响另一个。
我对这个一窍不通。这是怎么回事?
更新:
基于评论和问题:
当我 运行 我的程序 4 次时,2-3 次它会挂起 10-15 秒,1-2 次它会正常运行(并且不会打印 "Sleep: {0:0.00}s"。)
Thread.Count
确实上升了,但是无论挂起都会发生这种情况。我刚刚有一个 运行 它没有挂起,Thread.Count
从 24 开始,1 秒后上升到 40,大约 22 秒短任务正常完成,然后 Thread.Count
wend在接下来的 40 秒内慢慢下降到 22。
更多代码,完整代码可在下面的 link 中找到。起始客户:
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
,我都尝试了上述所有方法,没有任何区别。
更新 2:CODE!
没有看到更多的代码,很难做出进一步的猜测,但我想总结一下评论,这可能对以后的其他人有所帮助:
我们发现 the
ThreadPool
stuttering 不是问题,因为ThreadPool.SetMinThreads(500, 500)
没有帮助。您的任务流程中是否有任何
SynchronizationContext
?将Debug.Assert(SyncrhonizationContext.Current == null)
放在各处进行检查。每个await
. 使用 您的代码中是否使用了
.Wait
、.WaitOne
、.WaitAll
、WaitAny
、.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 (unlikeTask.Delay
, which usesSystem.Threading.Timer
, which in turn uses managedTimerQueue
). It's available here as a gist。随意尝试使用TaskExt.Delay
而不是标准的Task.Delay
。计时器回调已发布到ThreadPool
,因此本实验仍应使用ThreadPool.SetMinThreads(500, 500)
。我怀疑这会不会有什么不同,但我很想知道。
ConfigureAwait(false)