使用可变延迟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 https://github.com/Dorus/taskDelayProblem.
为了完整起见,将老问题留在下面。
我正在运行一个从网络流读取的任务,然后延迟 20 毫秒,然后再次读取(执行 500 次读取,这应该需要大约 10 秒)。当我只读取 1 个任务时,这种方法效果很好,但当我运行多个任务时,会发生奇怪的事情,其中一些任务有很长(60 秒)的延迟。我的 ms-delay 任务突然中途挂起。
我正在运行以下命令code https://github.com/Dorus/taskDelayProblem(简化):
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 任务我认为我可以忽略。
还有一些其他事情正在发生,例如第二个控制台应用程序写入网络流,但一个控制台应用程序不应影响另一个控制台应用程序。
我对此完全一无所知。到底是怎么回事?
Update:
根据评论和问题:
当我运行程序 4 次时,2-3 次它将挂起 10-15 秒,1-2 次它将正常运行(并且不会打印“Sleep: {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)
with await Task.Run(() => Thread.Sleep(20))
Thread.Count
现在从 29 到 43,然后又回到 24,但是在多个符文中它永远不会挂起。
有还是没有ThreadPool.SetMinThreads(500, 500)
, using TaskExt.Delay通过 诺塞拉蒂 https://gist.github.com/noserati/bf9432246cb9f0122c00它不会挂起。 (也就是说,即使切换 1 行代码有时也会阻止它挂起,只有在我重新启动项目 4 次后才会随机继续,但我现在已经连续尝试了 6 次,没有任何问题)。
我已经尝试过上面的所有内容,有或没有ThreadPool.SetMinThreads
到目前为止,没有任何区别。
更新2:CODE! https://github.com/Dorus/taskDelayProblem